Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'truffle-head' into truffle-new-instrumentation
Browse files Browse the repository at this point in the history
chrisseaton committed Dec 16, 2015
2 parents 001ca61 + a6c404b commit 877da98
Showing 104 changed files with 2,449 additions and 1,476 deletions.
63 changes: 49 additions & 14 deletions core/src/main/java/org/jruby/Main.java
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
package org.jruby;

import org.jruby.exceptions.MainExitException;
import org.jruby.exceptions.JumpException;
import org.jruby.exceptions.RaiseException;
import org.jruby.exceptions.ThreadKill;
import org.jruby.main.DripMain;
@@ -205,9 +206,14 @@ public static void main(String[] args) {
if (status.isExit()) {
System.exit(status.getStatus());
}
} catch (RaiseException rj) {
System.exit(handleRaiseException(rj));
} catch (Throwable t) {
}
catch (RaiseException ex) {
System.exit( handleRaiseException(ex) );
}
catch (JumpException ex) {
System.exit( handleUnexpectedJump(ex) );
}
catch (Throwable t) {
// If a Truffle exception gets this far it's a hard failure - don't try and dress it up as a Ruby exception

if (main.config.getCompileMode() == RubyInstanceConfig.CompileMode.TRUFFLE) {
@@ -516,25 +522,54 @@ private void doCheckSecurityManager() {
* run should be System.err. In order to avoid the Ruby err being closed and unable
* to write, we use System.err unconditionally.
*
* @param rj
* @param ex
* @return
*/
protected static int handleRaiseException(RaiseException rj) {
RubyException raisedException = rj.getException();
Ruby runtime = raisedException.getRuntime();
if (runtime.getSystemExit().isInstance(raisedException)) {
protected static int handleRaiseException(final RaiseException ex) {
RubyException raisedException = ex.getException();
final Ruby runtime = raisedException.getRuntime();
if ( runtime.getSystemExit().isInstance(raisedException) ) {
IRubyObject status = raisedException.callMethod(runtime.getCurrentContext(), "status");
if (status != null && !status.isNil()) {
if (status != null && ! status.isNil()) {
return RubyNumeric.fix2int(status);
} else {
return 0;
}
} else {
System.err.print(runtime.getInstanceConfig().getTraceType().printBacktrace(raisedException, runtime.getPosix().isatty(FileDescriptor.err)));
return 1;
return 0;
}
System.err.print(runtime.getInstanceConfig().getTraceType().printBacktrace(raisedException, runtime.getPosix().isatty(FileDescriptor.err)));
return 1;
}

private static int handleUnexpectedJump(final JumpException ex) {
if ( ex instanceof JumpException.SpecialJump ) { // ex == JumpException.SPECIAL_JUMP
System.err.println("Unexpected break: " + ex);
}
else if ( ex instanceof JumpException.FlowControlException ) {
// NOTE: assuming a single global runtime main(args) should have :
if ( Ruby.isGlobalRuntimeReady() ) {
final Ruby runtime = Ruby.getGlobalRuntime();
RaiseException raise = ((JumpException.FlowControlException) ex).buildException(runtime);
if ( raise != null ) handleRaiseException(raise);
}
else {
System.err.println("Unexpected jump: " + ex);
}
}
else {
System.err.println("Unexpected: " + ex);
}

final StackTraceElement[] trace = ex.getStackTrace();
if ( trace != null && trace.length > 0 ) {
System.err.println( ThreadContext.createRawBacktraceStringFromThrowable(ex, false) );
}
else {
System.err.println("HINT: to get backtrace for jump exceptions run with -Xjump.backtrace=true");
}

// TODO: should match MRI (>= 2.2.3) exit status - @see ruby/test_enum.rb#test_first
return 2;
}

public static void printTruffleTimeMetric(String id) {
if (Options.TRUFFLE_METRICS_TIME.load()) {
final long millis = System.currentTimeMillis();
26 changes: 20 additions & 6 deletions core/src/main/java/org/jruby/Ruby.java
Original file line number Diff line number Diff line change
@@ -1614,7 +1614,9 @@ private void initExceptions() {
typeError = defineClassIfAllowed("TypeError", standardError);
argumentError = defineClassIfAllowed("ArgumentError", standardError);
indexError = defineClassIfAllowed("IndexError", standardError);
stopIteration = defineClassIfAllowed("StopIteration", indexError);
if (profile.allowClass("StopIteration")) {
stopIteration = RubyStopIteration.createStopIterationClass(this, indexError);
}
syntaxError = defineClassIfAllowed("SyntaxError", scriptError);
loadError = defineClassIfAllowed("LoadError", scriptError);
notImplementedError = defineClassIfAllowed("NotImplementedError", scriptError);
@@ -4075,7 +4077,7 @@ private RaiseException newLightweightErrnoException(RubyClass exceptionClass, St
if (RubyInstanceConfig.ERRNO_BACKTRACE) {
return new RaiseException(this, exceptionClass, message, true);
} else {
return new RaiseException(this, exceptionClass, ERRNO_BACKTRACE_MESSAGE, RubyArray.newEmptyArray(this), true);
return new RaiseException(this, exceptionClass, ERRNO_BACKTRACE_MESSAGE, disabledBacktrace(), true);
}
}

@@ -4090,12 +4092,24 @@ private RaiseException newLightweightErrnoException(RubyClass exceptionClass, St
*
* @param message the message for the exception
*/
public RaiseException newLightweightStopIterationError(String message) {
public RaiseException newStopIteration(IRubyObject result, String message) {
final ThreadContext context = getCurrentContext();
if (RubyInstanceConfig.STOPITERATION_BACKTRACE) {
return new RaiseException(this, stopIteration, message, true);
} else {
return new RaiseException(this, stopIteration, STOPIERATION_BACKTRACE_MESSAGE, RubyArray.newEmptyArray(this), true);
RubyException ex = RubyStopIteration.newInstance(context, result, message);
return new RaiseException(ex);
}
if ( message == null ) message = STOPIERATION_BACKTRACE_MESSAGE;
RubyException ex = RubyStopIteration.newInstance(context, result, message);
return new RaiseException(ex, disabledBacktrace());
}

@Deprecated
public RaiseException newLightweightStopIterationError(String message) {
return newStopIteration(null, message);
}

private IRubyObject disabledBacktrace() {
return RubyArray.newEmptyArray(this);
}

// Equivalent of Data_Wrap_Struct
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyArray.java
Original file line number Diff line number Diff line change
@@ -2321,7 +2321,7 @@ public RubyArray collectBang(ThreadContext context, Block block) {
*/
@JRubyMethod(name = "collect!")
public IRubyObject collect_bang(ThreadContext context, Block block) {
return block.isGiven() ? collectBang(context, block) : enumeratorize(context.runtime, this, "collect!");
return block.isGiven() ? collectBang(context, block) : enumeratorizeWithSize(context, this, "collect!", enumLengthFn());
}

/** rb_ary_collect_bang
3 changes: 3 additions & 0 deletions core/src/main/java/org/jruby/RubyClass.java
Original file line number Diff line number Diff line change
@@ -1138,6 +1138,9 @@ public static void checkInheritable(IRubyObject superClass) {
if (((RubyClass)superClass).isSingleton()) {
throw superClass.getRuntime().newTypeError("can't make subclass of virtual class");
}
if (superClass == superClass.getRuntime().getClassClass()) {
throw superClass.getRuntime().newTypeError("can't make subclass of Class");
}
}

public final ObjectMarshal getMarshal() {
2 changes: 2 additions & 0 deletions core/src/main/java/org/jruby/RubyEnumerable.java
Original file line number Diff line number Diff line change
@@ -1201,6 +1201,7 @@ public static IRubyObject max_by(ThreadContext context, IRubyObject self, final

@JRubyMethod
public static IRubyObject max_by(ThreadContext context, IRubyObject self, IRubyObject arg, final Block block) {
if (arg == context.nil) return singleExtentBy(context, self, "max", SORT_MAX, block);
// TODO: Replace with an implementation (quickselect, etc) which requires O(k) memory rather than O(n) memory
RubyArray sorted = (RubyArray)sort_by(context, self, block);
return ((RubyArray) sorted.last(arg)).reverse();
@@ -1213,6 +1214,7 @@ public static IRubyObject min_by(ThreadContext context, IRubyObject self, final

@JRubyMethod
public static IRubyObject min_by(ThreadContext context, IRubyObject self, IRubyObject arg, final Block block) {
if (arg == context.nil) return singleExtentBy(context, self, "min", SORT_MIN, block);
// TODO: Replace with an implementation (quickselect, etc) which requires O(k) memory rather than O(n) memory
RubyArray sorted = (RubyArray)sort_by(context, self, block);
return sorted.first(arg);
112 changes: 57 additions & 55 deletions core/src/main/java/org/jruby/RubyEnumerator.java
Original file line number Diff line number Diff line change
@@ -95,22 +95,22 @@ public IRubyObject allocate(Ruby runtime, RubyClass klass) {
private RubyEnumerator(Ruby runtime, RubyClass type) {
super(runtime, type);
object = runtime.getNil();
initialize(runtime.getNil(), RubyString.newEmptyString(runtime), IRubyObject.NULL_ARRAY);
initialize(runtime, runtime.getNil(), RubyString.newEmptyString(runtime), IRubyObject.NULL_ARRAY);
}

private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[]args, IRubyObject size) {
private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[] args, IRubyObject size) {
super(runtime, type);
initialize20(object, method, args, size, null);
initialize20(runtime, object, method, args, size, null);
}

private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[]args, SizeFn sizeFn) {
private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[] args, SizeFn sizeFn) {
super(runtime, type);
initialize20(object, method, args, null, sizeFn);
initialize20(runtime, object, method, args, null, sizeFn);
}

private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[]args) {
private RubyEnumerator(Ruby runtime, RubyClass type, IRubyObject object, IRubyObject method, IRubyObject[] args) {
super(runtime, type);
initialize(object, method, args);
initialize(runtime, object, method, args);
}

/**
@@ -186,13 +186,13 @@ public IRubyObject initialize20(ThreadContext context, IRubyObject[] args, Block
size = args[0];
args = Arrays.copyOfRange(args, 1, args.length);

if (!(size.isNil() || size.respondsTo("call")) &&
!(runtime.getFloat().isInstance(size) && ((RubyFloat)size).getDoubleValue() == Float.POSITIVE_INFINITY) &&
!(size instanceof RubyInteger)) {
if ( ! (size.isNil() || size.respondsTo("call")) &&
! (runtime.getFloat().isInstance(size) && ((RubyFloat) size).getDoubleValue() == Float.POSITIVE_INFINITY) &&
! (size instanceof RubyInteger) ) {
throw runtime.newTypeError(size, runtime.getInteger());
}
}
object = context.runtime.getGenerator().newInstance(context, IRubyObject.NULL_ARRAY, block);
object = runtime.getGenerator().newInstance(context, IRubyObject.NULL_ARRAY, block);

} else {
Arity.checkArgumentCount(runtime, args, 1, -1);
@@ -205,7 +205,7 @@ public IRubyObject initialize20(ThreadContext context, IRubyObject[] args, Block
}
}

return initialize20(object, method, args, size, null);
return initialize20(runtime, object, method, args, size, null);
}

public IRubyObject initialize(ThreadContext context, IRubyObject object, IRubyObject method) {
@@ -220,9 +220,8 @@ public IRubyObject initialize19(ThreadContext context, IRubyObject object, IRuby
public IRubyObject initialize20(ThreadContext context, IRubyObject object, IRubyObject method, Block block) {
if (block.isGiven()) {
throw context.runtime.newArgumentError(2, 1);
} else {
return initialize(object, method, NULL_ARRAY);
}
return initialize(context.runtime, object, method, NULL_ARRAY);
}

public IRubyObject initialize(ThreadContext context, IRubyObject object, IRubyObject method, IRubyObject methodArg) {
@@ -237,9 +236,8 @@ public IRubyObject initialize19(ThreadContext context, IRubyObject object, IRuby
public IRubyObject initialize20(ThreadContext context, IRubyObject object, IRubyObject method, IRubyObject methodArg, Block block) {
if (block.isGiven()) {
throw context.runtime.newArgumentError(3, 1);
} else {
return initialize(object, method, new IRubyObject[] { methodArg });
}
return initialize(context.runtime, object, method, new IRubyObject[] { methodArg });
}

public IRubyObject initialize(ThreadContext context, IRubyObject[] args) {
@@ -250,12 +248,11 @@ public IRubyObject initialize19(ThreadContext context, IRubyObject[] args, Block
return initialize20(context, args, block);
}

private IRubyObject initialize(IRubyObject object, IRubyObject method, IRubyObject[] methodArgs) {
return initialize20(object, method, methodArgs, null, null);
private IRubyObject initialize(Ruby runtime, IRubyObject object, IRubyObject method, IRubyObject[] methodArgs) {
return initialize20(runtime, object, method, methodArgs, null, null);
}

private IRubyObject initialize20(IRubyObject object, IRubyObject method, IRubyObject[] methodArgs, IRubyObject size, SizeFn sizeFn) {
final Ruby runtime = getRuntime();
private IRubyObject initialize20(Ruby runtime, IRubyObject object, IRubyObject method, IRubyObject[] methodArgs, IRubyObject size, SizeFn sizeFn) {
this.object = object;
this.method = method.asJavaString();
this.methodArgs = methodArgs;
@@ -438,7 +435,7 @@ private IRubyObject with_index_common(ThreadContext context, final Block block,

@JRubyMethod
public IRubyObject each_with_index(ThreadContext context, final Block block) {
return with_index_common(context, block, "each_with_index", context.runtime.getNil());
return with_index_common(context, block, "each_with_index", context.nil);
}

public IRubyObject with_index(ThreadContext context, final Block block) {
@@ -447,7 +444,7 @@ public IRubyObject with_index(ThreadContext context, final Block block) {

@JRubyMethod(name = "with_index")
public IRubyObject with_index19(ThreadContext context, final Block block) {
return with_index_common(context, block, "with_index", context.runtime.getNil());
return with_index_common(context, block, "with_index", context.nil);
}

@JRubyMethod(name = "with_index")
@@ -615,15 +612,15 @@ protected IRubyObject get() {
}

private void checkIndex() throws RaiseException {
if (index >= array.size()) throw runtime.newLightweightStopIterationError("stop iteration");
if (index >= array.size()) throw runtime.newStopIteration(array, null);
}
}

private static class ThreadedNexter extends Nexter implements Runnable {
private static final boolean DEBUG = false;

/** sync queue to wait for values */
private SynchronousQueue<IRubyObject> out = new SynchronousQueue<IRubyObject>();
final SynchronousQueue<IRubyObject> out = new SynchronousQueue<IRubyObject>();

/** thread that's executing this Nexter */
private volatile Thread thread;
@@ -640,6 +637,9 @@ private static class ThreadedNexter extends Nexter implements Runnable {
/** the last value we got, used for peek */
private IRubyObject lastValue;

/** the block return value, to be fed as StopIteration#result */
private volatile IRubyObject stopValue;

/** Exception used for unrolling the iteration on terminate */
private static class TerminateEnumeration extends RuntimeException implements Unrescuable {}

@@ -729,7 +729,7 @@ private IRubyObject returnValue(IRubyObject value) {
// if it's the NEVER object, raise StopIteration
if (value == NEVER) {
doneObject = value;
throw runtime.newLightweightStopIterationError("stop iteration");
throw runtime.newStopIteration(stopValue, "iteration reached an end");
}

// if it's an exception, raise it
@@ -754,38 +754,41 @@ public void run() {
IRubyObject finalObject = NEVER;

try {
IRubyObject oldExc = runtime.getGlobalVariables().get("$!"); // Save $!
final IRubyObject oldExc = runtime.getGlobalVariables().get("$!"); // Save $!
final TerminateEnumeration terminateEnumeration = new TerminateEnumeration();
try {
object.callMethod(context, method, methodArgs, CallBlock.newCallClosure(object, object.getMetaClass(), Signature.OPTIONAL, new BlockCallback() {
@Override
public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
try {
if (DEBUG) System.out.println(Thread.currentThread().getName() + ": exchanging: " + Arrays.toString(args));
if (die) throw terminateEnumeration;
out.put(RubyEnumerable.packEnumValues(context, args));
if (die) throw terminateEnumeration;
} catch (InterruptedException ie) {
if (DEBUG) System.out.println(Thread.currentThread().getName() + ": interrupted");

throw terminateEnumeration;
}

IRubyObject feedValue = getFeedValue();
setFeedValue(context.nil);
return feedValue;
Block generatorClosure = CallBlock.newCallClosure(object, object.getMetaClass(), Signature.OPTIONAL, new BlockCallback() {

public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block) {
try {
if (DEBUG) System.out.println(Thread.currentThread().getName() + ": exchanging: " + Arrays.toString(args));
if (die) throw terminateEnumeration;
out.put( RubyEnumerable.packEnumValues(context, args) );
if (die) throw terminateEnumeration;
}
}, context));
} catch (TerminateEnumeration te) {
if (te != terminateEnumeration) {
throw te;
catch (InterruptedException ie) {
if (DEBUG) System.out.println(Thread.currentThread().getName() + ": interrupted");

throw terminateEnumeration;
}

IRubyObject feedValue = getFeedValue();
setFeedValue(context.nil);
return feedValue;
}
}, context);
try {
this.stopValue = object.callMethod(context, method, methodArgs, generatorClosure);
}
catch (TerminateEnumeration te) {
if (te != terminateEnumeration) throw te;
// ignore, we're shutting down
} catch (RaiseException re) {
}
catch (RaiseException re) {
if (DEBUG) System.out.println(Thread.currentThread().getName() + ": exception at toplevel: " + re.getException());
finalObject = re.getException();
runtime.getGlobalVariables().set("$!", oldExc); // Restore $!
} catch (Throwable t) {
}
catch (Throwable t) {
if (DEBUG) {
System.out.println(Thread.currentThread().getName() + ": exception at toplevel: " + t);
t.printStackTrace();
@@ -795,12 +798,11 @@ public IRubyObject call(ThreadContext context, IRubyObject[] args, Block block)

try {
if (!die) out.put(finalObject);
} catch (InterruptedException ie) {
// ignore
}
} finally {
// disassociate this Nexter with the thread running it
thread = null;
catch (InterruptedException ie) { /* ignore */ }
}
finally {
thread = null; // disassociate this Nexter with the thread running it
}
}
}
34 changes: 19 additions & 15 deletions core/src/main/java/org/jruby/RubyFloat.java
Original file line number Diff line number Diff line change
@@ -651,34 +651,38 @@ public IRubyObject op_le(ThreadContext context, double other) {
@JRubyMethod(name = "eql?", required = 1)
@Override
public IRubyObject eql_p(IRubyObject other) {
if (other instanceof RubyFloat) {
double b = ((RubyFloat) other).value;
if (Double.isNaN(value) || Double.isNaN(b)) {
return getRuntime().getFalse();
}
if (value == b) {
return getRuntime().getTrue();
}
}
return getRuntime().getFalse();
return getRuntime().newBoolean( equals(other) );
}

@Override
public boolean equals(Object other) {
return (other instanceof RubyFloat) && equals((RubyFloat) other);
}

private boolean equals(RubyFloat that) {
if ( Double.isNaN(this.value) || Double.isNaN(that.value) ) return false;
final double val1 = this.value == -0.0 ? 0.0 : this.value;
final double val2 = that.value == -0.0 ? 0.0 : that.value;
return Double.doubleToLongBits(val1) == Double.doubleToLongBits(val2);
}

/** flo_hash
*
*/
@JRubyMethod(name = "hash")
@Override
public RubyFixnum hash() {
return getRuntime().newFixnum(hashCode());
return getRuntime().newFixnum( hashCode() );
}

@Override
public final int hashCode() {
long l = Double.doubleToLongBits(value);
return (int)(l ^ l >>> 32);
}
final double val = value == 0.0 ? -0.0 : value;
final long l = Double.doubleToLongBits(val);
return (int) ( l ^ l >>> 32 );
}

/** flo_fo
/** flo_fo
*
*/
@JRubyMethod(name = "to_f")
19 changes: 10 additions & 9 deletions core/src/main/java/org/jruby/RubyKernel.java
Original file line number Diff line number Diff line change
@@ -1279,27 +1279,28 @@ public static RubyProc proc_1_9(ThreadContext context, IRubyObject recv, Block b

@JRubyMethod(name = "loop", module = true, visibility = PRIVATE)
public static IRubyObject loop(ThreadContext context, IRubyObject recv, Block block) {
Ruby runtime = context.runtime;
if (!block.isGiven()) {
if ( ! block.isGiven() ) {
return RubyEnumerator.enumeratorizeWithSize(context, recv, "loop", loopSizeFn(context));
}
IRubyObject nil = runtime.getNil();
RubyClass stopIteration = runtime.getStopIteration();
final Ruby runtime = context.runtime;
IRubyObject oldExc = runtime.getGlobalVariables().get("$!"); // Save $!
try {
while (true) {
block.yieldSpecific(context);

context.pollThreadEvents();
}
} catch (RaiseException ex) {
if (!stopIteration.op_eqq(context, ex.getException()).isTrue()) {
throw ex;
} else {
}
catch (RaiseException ex) {
final RubyClass StopIteration = runtime.getStopIteration();
if ( StopIteration.isInstance(ex.getException()) ) {
runtime.getGlobalVariables().set("$!", oldExc); // Restore $!
return context.nil;
}
else {
throw ex;
}
}
return nil;
}

private static SizeFn loopSizeFn(final ThreadContext context) {
78 changes: 78 additions & 0 deletions core/src/main/java/org/jruby/RubyStopIteration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/***** BEGIN LICENSE BLOCK *****
* Version: EPL 1.0/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Eclipse Public
* License Version 1.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.eclipse.org/legal/epl-v10.html
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the EPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the EPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****/

package org.jruby;

import org.jruby.anno.JRubyMethod;
import org.jruby.anno.JRubyClass;
import org.jruby.runtime.Block;
import org.jruby.runtime.ObjectAllocator;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;

/**
* Ruby's StopIteration exception.
* @see RubyEnumerator
* @author kares
*/
@JRubyClass(name="StopIteration", parent="IndexError")
public class RubyStopIteration extends RubyException {

private static final ObjectAllocator ALLOCATOR = new ObjectAllocator() {
@Override
public IRubyObject allocate(Ruby runtime, RubyClass klass) {
return new RubyStopIteration(runtime, klass);
}
};

static RubyClass createStopIterationClass(Ruby runtime, RubyClass superClass) {
RubyClass StopIteration = runtime.defineClass("StopIteration", superClass, ALLOCATOR);
StopIteration.defineAnnotatedMethods(RubyStopIteration.class);
return StopIteration;
}

public static RubyStopIteration newInstance(ThreadContext context, IRubyObject result, String message) {
final Ruby runtime = context.runtime;
RubyClass StopIteration = runtime.getStopIteration();
final IRubyObject msg = message == null ? context.nil : runtime.newString(message);
RubyStopIteration instance = (RubyStopIteration)
StopIteration.newInstance(context, msg, Block.NULL_BLOCK);
instance.result = result;
return instance;
}

private IRubyObject result;

protected RubyStopIteration(Ruby runtime, RubyClass exceptionClass) {
super(runtime, exceptionClass);
}

@JRubyMethod
public IRubyObject result() {
return result == null ? getRuntime().getNil() : result;
}

}
23 changes: 16 additions & 7 deletions core/src/main/java/org/jruby/RubyTime.java
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -81,9 +82,10 @@
@JRubyClass(name="Time", include="Comparable")
public class RubyTime extends RubyObject {
public static final String UTC = "UTC";
public static final BigDecimal ONE_MILLION_BD = new BigDecimal(1000000);
@Deprecated // no longer used
public static final BigDecimal ONE_MILLION_BD = BigDecimal.valueOf(1000000);
public static final BigInteger ONE_MILLION_BI = BigInteger.valueOf(1000000);
public static final BigDecimal ONE_THOUSAND_BD = new BigDecimal(1000);
public static final BigDecimal ONE_THOUSAND_BD = BigDecimal.valueOf(1000);
private DateTime dt;
private long nsec;

@@ -1058,12 +1060,19 @@ public static IRubyObject at(ThreadContext context, IRubyObject recv, IRubyObjec
long numerator = rational.numerator(context).convertToInteger().getLongValue();
long denominator = rational.denominator(context).convertToInteger().getLongValue();

BigDecimal accurateSeconds = new BigDecimal(numerator).divide(new BigDecimal(denominator));
BigDecimal accurateMillis = accurateSeconds.multiply(ONE_THOUSAND_BD);
BigInteger integralMillis = accurateMillis.toBigInteger();
BigInteger remainingNanos = accurateMillis.multiply(ONE_MILLION_BD).toBigInteger().subtract(integralMillis.multiply(ONE_MILLION_BI));
final BigDecimal secs;
if ( numerator <= Integer.MAX_VALUE ) {
secs = new BigDecimal(numerator * 1000);
}
else {
secs = BigDecimal.valueOf(numerator).multiply(ONE_THOUSAND_BD);
}
final BigDecimal millis = secs.divide(BigDecimal.valueOf(denominator), 12, RoundingMode.DOWN);

final BigInteger roundMillis = millis.toBigInteger();
BigInteger remainingNanos = millis.movePointRight(6).toBigInteger().subtract( roundMillis.multiply(ONE_MILLION_BI) );

millisecs = integralMillis.longValue();
millisecs = roundMillis.longValue();
nanosecs = remainingNanos.longValue();
}
} else {
9 changes: 9 additions & 0 deletions core/src/main/java/org/jruby/ast/StrNode.java
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@
public class StrNode extends Node implements ILiteralNode, SideEffectFree {
private final ByteList value;
private final int codeRange;
private boolean frozen;

public StrNode(ISourcePosition position, ByteList value) {
this(position, value, StringSupport.codeRangeScan(value.getEncoding(), value));
@@ -103,4 +104,12 @@ public int getCodeRange() {
public List<Node> childNodes() {
return EMPTY_LIST;
}

public boolean isFrozen() {
return frozen;
}

public void setFrozen(boolean frozen) {
this.frozen = frozen;
}
}
21 changes: 9 additions & 12 deletions core/src/main/java/org/jruby/ir/IRBuilder.java
Original file line number Diff line number Diff line change
@@ -2087,6 +2087,7 @@ private void handleBreakAndReturnsInLambdas() {
// --> IRRuntimeHelpers.handleBreakAndReturnsInLambdas(context, scope, bj, blockType)
Variable ret = createTemporaryVariable();
addInstr(new RuntimeHelperCall(ret, RuntimeHelperCall.Methods.HANDLE_BREAK_AND_RETURNS_IN_LAMBDA, new Operand[]{exc} ));
addInstr(new RethrowSavedExcInLambdaInstr());
addInstr(new ReturnInstr(ret));

// End
@@ -2531,14 +2532,7 @@ public Operand buildHash(HashNode hashNode) {
addInstr(new RuntimeHelperCall(hash, MERGE_KWARGS, new Operand[] { hash, splat}));
continue;
} else {
// TODO: This isn't super pretty. If AST were aware of literal hash string keys being "special"
// it could have an appropriate AST node for frozen string and this code would just go away.
if (key instanceof StrNode) {
StrNode strKey = (StrNode)key;
keyOperand = new FrozenString(strKey.getValue(), strKey.getCodeRange());
} else {
keyOperand = buildWithOrder(key, hasAssignments);
}
keyOperand = buildWithOrder(key, hasAssignments);
}

args.add(new KeyValuePair<>(keyOperand, buildWithOrder(pair.getValue(), hasAssignments)));
@@ -3314,10 +3308,13 @@ public Operand buildSplat(SplatNode splatNode) {
}

public Operand buildStr(StrNode strNode) {
if (strNode instanceof FileNode) {
return new Filename();
}
return copyAndReturnValue(new StringLiteral(strNode.getValue(), strNode.getCodeRange()));
if (strNode instanceof FileNode) return new Filename();

Operand literal = strNode.isFrozen() ?
new FrozenString(strNode.getValue(), strNode.getCodeRange()) :
new StringLiteral(strNode.getValue(), strNode.getCodeRange());

return copyAndReturnValue(literal);
}

private Operand buildSuperInstr(Operand block, Operand[] args) {
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/IRVisitor.java
Original file line number Diff line number Diff line change
@@ -117,6 +117,7 @@ private void error(Object object) {
public void RestArgMultipleAsgnInstr(RestArgMultipleAsgnInstr restargmultipleasgninstr) { error(restargmultipleasgninstr); }
public void RestoreBindingVisibilityInstr(RestoreBindingVisibilityInstr instr) { error(instr); }
public void ReturnInstr(ReturnInstr returninstr) { error(returninstr); }
public void RethrowSavedExcInLambdaInstr(RethrowSavedExcInLambdaInstr instr) { error(instr); }
public void RuntimeHelperCall(RuntimeHelperCall runtimehelpercall) { error(runtimehelpercall); }
public void SaveBindingVisibilityInstr(SaveBindingVisibilityInstr instr) { error(instr); }
public void SearchConstInstr(SearchConstInstr searchconstinstr) { error(searchconstinstr); }
1 change: 1 addition & 0 deletions core/src/main/java/org/jruby/ir/Operation.java
Original file line number Diff line number Diff line change
@@ -213,6 +213,7 @@ public enum Operation {
POP_BINDING(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
SAVE_BINDING_VIZ(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
RESTORE_BINDING_VIZ(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
RETHROW_SAVED_EXC_IN_LAMBDA(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
TOGGLE_BACKTRACE(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),
UPDATE_BLOCK_STATE(OpFlags.f_is_book_keeping_op | OpFlags.f_has_side_effect),

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.jruby.ir.instructions;

import org.jruby.ir.IRVisitor;
import org.jruby.ir.Operation;
import org.jruby.ir.persistence.IRReaderDecoder;
import org.jruby.ir.transformations.inlining.CloneInfo;

public class RethrowSavedExcInLambdaInstr extends NoOperandInstr implements FixedArityInstr {
public RethrowSavedExcInLambdaInstr() {
super(Operation.RETHROW_SAVED_EXC_IN_LAMBDA);
}

@Override
public Instr clone(CloneInfo ii) {
return this; // FIXME: Needs update
}

public static RethrowSavedExcInLambdaInstr decode(IRReaderDecoder d) {
return new RethrowSavedExcInLambdaInstr();
}

@Override
public void visit(IRVisitor visitor) {
visitor.RethrowSavedExcInLambdaInstr(this);
}
}
Original file line number Diff line number Diff line change
@@ -114,12 +114,15 @@ public IRubyObject interpret(ThreadContext context, Block block, IRubyObject sel
int n = instrs.length;
int ipc = 0;
Object exception = null;
boolean acceptsKeywordArgument = interpreterContext.receivesKeywordArguments();

if (interpreterContext.receivesKeywordArguments()) IRRuntimeHelpers.frobnicateKwargsArgument(context, interpreterContext.getRequiredArgsCount(), args);
// Blocks with explicit call protocol shouldn't do this before args are prepared
if (acceptsKeywordArgument && (block == null || !interpreterContext.hasExplicitCallProtocol())) {
IRRuntimeHelpers.frobnicateKwargsArgument(context, interpreterContext.getRequiredArgsCount(), args);
}

StaticScope currScope = interpreterContext.getStaticScope();
DynamicScope currDynScope = context.getCurrentScope();
boolean acceptsKeywordArgument = interpreterContext.receivesKeywordArguments();

// Init profiling this scope
boolean debug = IRRuntimeHelpers.isDebug();
@@ -185,7 +188,7 @@ public IRubyObject interpret(ThreadContext context, Block block, IRubyObject sel
args = IRRuntimeHelpers.prepareFixedBlockArgs(context, block, args);
break;
case PREPARE_BLOCK_ARGS:
args = IRRuntimeHelpers.prepareBlockArgs(context, block, args);
args = IRRuntimeHelpers.prepareBlockArgs(context, block, args, acceptsKeywordArgument);
break;
default:
processBookKeepingOp(context, block, instr, operation, name, args, self, blockArg, implClass, currDynScope, temp, currScope);
@@ -375,6 +378,9 @@ protected static void processBookKeepingOp(ThreadContext context, Block block, I
case POP_BINDING:
context.popScope();
break;
case RETHROW_SAVED_EXC_IN_LAMBDA:
IRRuntimeHelpers.rethrowSavedExcInLambda(context);
break; // may not be reachable
case THREAD_POLL:
if (IRRuntimeHelpers.inProfileMode()) Profiler.clockTick();
context.callThreadPoll();
Original file line number Diff line number Diff line change
@@ -23,8 +23,7 @@ public String getLabel() {

private boolean explicitCallProtocolSupported(IRScope scope) {
return scope instanceof IRMethod
// SSS: Turning this off till this is fully debugged
// || (scope instanceof IRClosure && !(scope instanceof IREvalScript))
|| (scope instanceof IRClosure && !(scope instanceof IREvalScript))
|| (scope instanceof IRModuleBody && !(scope instanceof IRMetaClassBody));
}

@@ -46,7 +45,11 @@ private void fixReturn(IRScope scope, ReturnBase i, ListIterator<Instr> instrs)
}
}

private void popSavedState(IRScope scope, boolean requireBinding, boolean requireFrame, Variable savedViz, Variable savedFrame, ListIterator<Instr> instrs) {
private void popSavedState(IRScope scope, boolean isGEB, boolean requireBinding, boolean requireFrame, Variable savedViz, Variable savedFrame, ListIterator<Instr> instrs) {
if (scope instanceof IRClosure && isGEB) {
// Add before RethrowSavedExcInLambdaInstr
instrs.previous();
}
if (requireBinding) instrs.add(new PopBindingInstr());
if (scope instanceof IRClosure) {
instrs.add(new PopBlockFrameInstr(savedFrame));
@@ -137,47 +140,44 @@ public Object execute(IRScope scope, Object... data) {
ListIterator<Instr> instrs = bb.getInstrs().listIterator();
while (instrs.hasNext()) {
i = instrs.next();
// Right now, we only support explicit call protocol on methods.
// So, non-local returns and breaks don't get here.
// Non-local-returns and breaks are tricky since they almost always
// throw an exception and we don't multiple pops (once before the
// return/break, and once when the exception is caught).
if (!bb.isExitBB() && i instanceof ReturnBase) {
if (requireBinding || requireFrame) {
fixReturn(scope, (ReturnBase)i, instrs);
}
// Breaks & non-local returns in blocks will throw exceptions
// and pops for them will be handled in the GEB
if (!bb.isExitBB() && i instanceof ReturnInstr) {
if (requireBinding) fixReturn(scope, (ReturnInstr)i, instrs);
// Add before the break/return
instrs.previous();
popSavedState(scope, requireBinding, requireFrame, savedViz, savedFrame, instrs);
popSavedState(scope, bb == geb, requireBinding, requireFrame, savedViz, savedFrame, instrs);
if (bb == geb) gebProcessed = true;
break;
}
}

if (bb.isExitBB() && !bb.isEmpty()) {
// Last instr could be a return -- so, move iterator one position back
if (i != null && i instanceof ReturnBase) {
if (requireBinding || requireFrame) {
fixReturn(scope, (ReturnBase)i, instrs);
}
if (i != null && i instanceof ReturnInstr) {
if (requireBinding) fixReturn(scope, (ReturnInstr)i, instrs);
instrs.previous();
}
popSavedState(scope, requireBinding, requireFrame, savedViz, savedFrame, instrs);
popSavedState(scope, bb == geb, requireBinding, requireFrame, savedViz, savedFrame, instrs);
if (bb == geb) gebProcessed = true;
}

if (!gebProcessed && bb == geb) {
} else if (!gebProcessed && bb == geb) {
// Add before throw-exception-instr which would be the last instr
if (i != null) {
// Assumption: Last instr should always be a control-transfer instruction
assert i.getOperation().transfersControl(): "Last instruction of GEB in scope: " + scope + " is " + i + ", not a control-xfer instruction";
instrs.previous();
}
popSavedState(scope, requireBinding, requireFrame, savedViz, savedFrame, instrs);
popSavedState(scope, true, requireBinding, requireFrame, savedViz, savedFrame, instrs);
}
}
}

/*
if (scope instanceof IRClosure) {
System.out.println(scope + " after acp: " + cfg.toStringInstrs());
}
*/

// This scope has an explicit call protocol flag now
scope.setExplicitCallProtocolFlag();

47 changes: 36 additions & 11 deletions core/src/main/java/org/jruby/ir/runtime/IRRuntimeHelpers.java
Original file line number Diff line number Diff line change
@@ -151,7 +151,7 @@ public static IRubyObject initiateNonLocalReturn(ThreadContext context, DynamicS
public static IRubyObject handleNonlocalReturn(StaticScope scope, DynamicScope dynScope, Object rjExc, Block.Type blockType) throws RuntimeException {
if (!(rjExc instanceof IRReturnJump)) {
Helpers.throwException((Throwable)rjExc);
return null;
return null; // Unreachable
} else {
IRReturnJump rj = (IRReturnJump)rjExc;

@@ -195,24 +195,41 @@ public static IRubyObject initiateBreak(ThreadContext context, DynamicScope dynS
public static IRubyObject handleBreakAndReturnsInLambdas(ThreadContext context, StaticScope scope, DynamicScope dynScope, Object exc, Block.Type blockType) throws RuntimeException {
if ((exc instanceof IRBreakJump) && inNonMethodBodyLambda(scope, blockType)) {
// We just unwound all the way up because of a non-local break
throw IRException.BREAK_LocalJumpError.getException(context.getRuntime());
context.setSavedExceptionInLambda(IRException.BREAK_LocalJumpError.getException(context.getRuntime()));
return null;
} else if (exc instanceof IRReturnJump && (blockType == null || inLambda(blockType))) {
// Ignore non-local return processing in non-lambda blocks.
// Methods have a null blocktype
return handleNonlocalReturn(scope, dynScope, exc, blockType);
try {
// Ignore non-local return processing in non-lambda blocks.
// Methods have a null blocktype
return handleNonlocalReturn(scope, dynScope, exc, blockType);
} catch (Throwable e) {
context.setSavedExceptionInLambda(e);
return null;
}
} else {
// Propagate
Helpers.throwException((Throwable)exc);
// should not get here
// Propagate the exception
context.setSavedExceptionInLambda((Throwable)exc);
return null;
}
}

@JIT
public static void rethrowSavedExcInLambda(ThreadContext context) {
// This rethrows the exception saved in handleBreakAndReturnsInLambda
// after additional code to pop frames, bindings, etc. are done.
Throwable exc = context.getSavedExceptionInLambda();
if (exc != null) {
// IMPORTANT: always clear!
context.setSavedExceptionInLambda(null);
Helpers.throwException(exc);
}
}

@JIT
public static IRubyObject handlePropagatedBreak(ThreadContext context, DynamicScope dynScope, Object bjExc, Block.Type blockType) {
if (!(bjExc instanceof IRBreakJump)) {
Helpers.throwException((Throwable)bjExc);
return null;
return null; // Unreachable
}

IRBreakJump bj = (IRBreakJump)bjExc;
@@ -1491,8 +1508,7 @@ public static IRubyObject[] prepareProcArgs(ThreadContext context, Block b, IRub
}
}

@JIT
public static IRubyObject[] prepareBlockArgs(ThreadContext context, Block block, IRubyObject[] args) {
public static IRubyObject[] prepareBlockArgsInternal(ThreadContext context, Block block, IRubyObject[] args) {
// This is the placeholder for scenarios
// not handled by specialized instructions.
if (args == null) {
@@ -1557,6 +1573,15 @@ public static IRubyObject[] prepareBlockArgs(ThreadContext context, Block block,
return args;
}

@JIT
public static IRubyObject[] prepareBlockArgs(ThreadContext context, Block block, IRubyObject[] args, boolean usesKwArgs) {
args = prepareBlockArgsInternal(context, block, args);
if (usesKwArgs) {
frobnicateKwargsArgument(context, block.getBody().getSignature().required(), args);
}
return args;
}

public static IRubyObject[] prepareFixedBlockArgs(ThreadContext context, Block block, IRubyObject[] args) {
if (args == null) {
return IRubyObject.NULL_ARRAY;
9 changes: 8 additions & 1 deletion core/src/main/java/org/jruby/ir/targets/JVMVisitor.java
Original file line number Diff line number Diff line change
@@ -1415,7 +1415,8 @@ public void PrepareBlockArgsInstr(PrepareBlockArgsInstr instr) {
jvmMethod().loadContext();
jvmMethod().loadSelfBlock();
jvmMethod().loadArgs();
jvmMethod().invokeIRHelper("prepareBlockArgs", sig(IRubyObject[].class, ThreadContext.class, Block.class, IRubyObject[].class));
jvmAdapter().ldc(((IRClosure)jvm.methodData().scope).receivesKeywordArgs());
jvmMethod().invokeIRHelper("prepareBlockArgs", sig(IRubyObject[].class, ThreadContext.class, Block.class, IRubyObject[].class, boolean.class));
jvmMethod().storeArgs();
}

@@ -1696,6 +1697,12 @@ public void RestoreBindingVisibilityInstr(RestoreBindingVisibilityInstr instr) {
jvmAdapter().invokevirtual(p(Binding.class), "setVisibility", sig(void.class, Visibility.class));
}

@Override
public void RethrowSavedExcInLambdaInstr(RethrowSavedExcInLambdaInstr instr) {
jvmMethod().loadContext();
jvmMethod().invokeIRHelper("rethrowSavedExcInLambda", sig(void.class, ThreadContext.class));
}

@Override
public void RuntimeHelperCall(RuntimeHelperCall runtimehelpercall) {
switch (runtimehelpercall.getHelperMethod()) {
7 changes: 7 additions & 0 deletions core/src/main/java/org/jruby/parser/ParserSupport.java
Original file line number Diff line number Diff line change
@@ -56,6 +56,7 @@
import org.jruby.runtime.DynamicScope;
import org.jruby.runtime.Signature;
import org.jruby.util.ByteList;
import org.jruby.util.KeyValuePair;
import org.jruby.util.RegexpOptions;
import org.jruby.util.StringSupport;
import org.jruby.util.cli.Options;
@@ -881,6 +882,12 @@ public DStrNode createDStrNode(ISourcePosition position) {
return new DStrNode(position, lexer.getEncoding());
}

public KeyValuePair<Node, Node> createKeyValue(Node key, Node value) {
if (key != null && key instanceof StrNode) ((StrNode) key).setFrozen(true);

return new KeyValuePair<>(key, value);
}

public Node asSymbol(ISourcePosition position, String value) {
return new SymbolNode(position, value, lexer.getEncoding(), lexer.getTokenCR());
}
12 changes: 6 additions & 6 deletions core/src/main/java/org/jruby/parser/RubyParser.java
Original file line number Diff line number Diff line change
@@ -5236,14 +5236,14 @@ public Object yyparse (RubyLexer yyLex) throws java.io.IOException {
};
states[610] = new ParserState() {
@Override public Object execute(ParserSupport support, RubyLexer lexer, Object yyVal, Object[] yyVals, int yyTop) {
yyVal = new KeyValuePair<Node,Node>(((Node)yyVals[-2+yyTop]), ((Node)yyVals[0+yyTop]));
yyVal = support.createKeyValue(((Node)yyVals[-2+yyTop]), ((Node)yyVals[0+yyTop]));
return yyVal;
}
};
states[611] = new ParserState() {
@Override public Object execute(ParserSupport support, RubyLexer lexer, Object yyVal, Object[] yyVals, int yyTop) {
Node label = support.asSymbol(support.getPosition(((Node)yyVals[0+yyTop])), ((String)yyVals[-1+yyTop]));
yyVal = new KeyValuePair<Node,Node>(label, ((Node)yyVals[0+yyTop]));
yyVal = support.createKeyValue(label, ((Node)yyVals[0+yyTop]));
return yyVal;
}
};
@@ -5252,9 +5252,9 @@ public Object yyparse (RubyLexer yyLex) throws java.io.IOException {
if (((Node)yyVals[-2+yyTop]) instanceof StrNode) {
DStrNode dnode = new DStrNode(support.getPosition(((Node)yyVals[-2+yyTop])), lexer.getEncoding());
dnode.add(((Node)yyVals[-2+yyTop]));
yyVal = new KeyValuePair<Node,Node>(new DSymbolNode(support.getPosition(((Node)yyVals[-2+yyTop])), dnode), ((Node)yyVals[0+yyTop]));
yyVal = support.createKeyValue(new DSymbolNode(support.getPosition(((Node)yyVals[-2+yyTop])), dnode), ((Node)yyVals[0+yyTop]));
} else if (((Node)yyVals[-2+yyTop]) instanceof DStrNode) {
yyVal = new KeyValuePair<Node,Node>(new DSymbolNode(support.getPosition(((Node)yyVals[-2+yyTop])), ((DStrNode)yyVals[-2+yyTop])), ((Node)yyVals[0+yyTop]));
yyVal = support.createKeyValue(new DSymbolNode(support.getPosition(((Node)yyVals[-2+yyTop])), ((DStrNode)yyVals[-2+yyTop])), ((Node)yyVals[0+yyTop]));
} else {
support.compile_error("Uknown type for assoc in strings: " + ((Node)yyVals[-2+yyTop]));
}
@@ -5264,7 +5264,7 @@ public Object yyparse (RubyLexer yyLex) throws java.io.IOException {
};
states[613] = new ParserState() {
@Override public Object execute(ParserSupport support, RubyLexer lexer, Object yyVal, Object[] yyVals, int yyTop) {
yyVal = new KeyValuePair<Node,Node>(null, ((Node)yyVals[0+yyTop]));
yyVal = support.createKeyValue(null, ((Node)yyVals[0+yyTop]));
return yyVal;
}
};
@@ -5293,7 +5293,7 @@ public Object yyparse (RubyLexer yyLex) throws java.io.IOException {
}
};
}
// line 2525 "RubyParser.y"
// line 2524 "RubyParser.y"

/** The parse method use an lexer stream and parse it to an AST node
* structure
11 changes: 5 additions & 6 deletions core/src/main/java/org/jruby/parser/RubyParser.y
Original file line number Diff line number Diff line change
@@ -2470,27 +2470,26 @@ assocs : assoc {

// Cons: [!null]
assoc : arg_value tASSOC arg_value {
$$ = new KeyValuePair<Node,Node>($1, $3);
$$ = support.createKeyValue($1, $3);
}
| tLABEL arg_value {
Node label = support.asSymbol(support.getPosition($2), $1);
$$ = new KeyValuePair<Node,Node>(label, $2);
$$ = support.createKeyValue(label, $2);
}
| tSTRING_BEG string_contents tLABEL_END arg_value {
if ($2 instanceof StrNode) {
DStrNode dnode = new DStrNode(support.getPosition($2), lexer.getEncoding());
dnode.add($2);
$$ = new KeyValuePair<Node,Node>(new DSymbolNode(support.getPosition($2), dnode), $4);
$$ = support.createKeyValue(new DSymbolNode(support.getPosition($2), dnode), $4);
} else if ($2 instanceof DStrNode) {
$$ = new KeyValuePair<Node,Node>(new DSymbolNode(support.getPosition($2), $<DStrNode>2), $4);
$$ = support.createKeyValue(new DSymbolNode(support.getPosition($2), $<DStrNode>2), $4);
} else {
support.compile_error("Uknown type for assoc in strings: " + $2);
}

}

| tDSTAR arg_value {
$$ = new KeyValuePair<Node,Node>(null, $2);
$$ = support.createKeyValue(null, $2);
}

operation : tIDENTIFIER | tCONSTANT | tFID
Original file line number Diff line number Diff line change
@@ -64,7 +64,7 @@ public InterpreterContext ensureInstrsReady() {

if (interpreterContext == null) {
interpreterContext = closure.getInterpreterContext();
hasCallProtocolIR = closure.getFlags().contains(IRFlags.HAS_EXPLICIT_CALL_PROTOCOL);
hasCallProtocolIR = false;
}
return interpreterContext;
}
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ public InterpreterContext ensureInstrsReady() {

if (interpreterContext == null) {
interpreterContext = closure.getInterpreterContext();
hasCallProtocolIR = closure.getFlags().contains(IRFlags.HAS_EXPLICIT_CALL_PROTOCOL);
hasCallProtocolIR = false;
}
return interpreterContext;
}
14 changes: 13 additions & 1 deletion core/src/main/java/org/jruby/runtime/ThreadContext.java
Original file line number Diff line number Diff line change
@@ -128,7 +128,10 @@ public static ThreadContext newContext(Ruby runtime) {

IRubyObject lastExitStatus;

private Block.Type currentBlockType;
// These two fields are required to support explicit call protocol
// (via IR instructions) for blocks.
private Block.Type currentBlockType; // See prepareBlockArgs code in IRRuntimeHelpers
private Throwable savedExcInLambda; // See handleBreakAndReturnsInLambda in IRRuntimeHelpers

public final SecureRandom secureRandom = getSecureRandom();

@@ -154,6 +157,7 @@ private ThreadContext(Ruby runtime) {
this.runtime = runtime;
this.nil = runtime.getNil();
this.currentBlockType = Block.Type.NORMAL;
this.savedExcInLambda = null;

if (runtime.getInstanceConfig().isProfilingEntireRun()) {
startProfiling();
@@ -218,6 +222,14 @@ public void setCurrentBlockType(Block.Type type) {
currentBlockType = type;
}

public Throwable getSavedExceptionInLambda() {
return savedExcInLambda;
}

public void setSavedExceptionInLambda(Throwable e) {
savedExcInLambda = e;
}

public CallType getLastCallType() {
return lastCallType;
}
17 changes: 17 additions & 0 deletions lib/ruby/truffle/jruby+truffle/README.md
Original file line number Diff line number Diff line change
@@ -92,6 +92,23 @@ activesupport's configuration file follows:
- shims
```
## Using the tool in CI
Assuming there are similar stored commands for a given gem:
```yaml
:stored_commands:
:ci:
- :setup
- :test
:setup:
- "git clone git@github.com:lucasocon/openweather.git"
- "jruby+truffle --dir openweather setup"
:test: "jruby+truffle --dir openweather run --require-pattern 'test/*_test.rb' -I test -- -e nil"
```
then `jruby+truffle --config openweather stored ci` can be used to run tests of the given gem in CI.

## Example step-by-step

```sh
Original file line number Diff line number Diff line change
@@ -81,8 +81,11 @@
- pathname
:stored_commands:
:ci:
- "jruby+truffle setup"
- :setup
- :test
:test: "jruby+truffle run --exclude-pattern 'jdom_engine' --require-pattern 'test/**/**/*_test.rb' -r exclude_tests -- -I test -e 'nil'"
:setup:
- "git clone -b 4-2-stable git@github.com:rails/rails.git"
- "jruby+truffle --dir rails/activesupport setup"
:test: "jruby+truffle --dir rails/activesupport run --exclude-pattern 'jdom_engine' --require-pattern 'test/**/**/*_test.rb' -r exclude_tests -- -I test -e 'nil'"


12 changes: 12 additions & 0 deletions lib/ruby/truffle/jruby+truffle/gem_configurations/openweather.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
:setup:
:file:
"bundler/gem_tasks.rb": nil
:stored_commands:
:ci:
- :setup
- :test
:setup:
- "git clone git@github.com:lucasocon/openweather.git"
- "jruby+truffle --dir openweather setup"
:test: "jruby+truffle --dir openweather run --require-pattern 'test/*_test.rb' -I test -- -e nil"

41 changes: 24 additions & 17 deletions lib/ruby/truffle/jruby+truffle/runner.rb
Original file line number Diff line number Diff line change
@@ -57,9 +57,11 @@ class JRubyTruffleRunner
mock_load_path: ['--mock-load-path PATH',
'Path of mocks & monkey-patches (prepended in $:, relative to --truffle_bundle_path)',
assign_new_value, 'mocks'],
use_fs_core: ['--[no-]use-fs-core', 'use core from the filesystem rather than the JAR',
use_fs_core: ['--[no-]use-fs-core', 'Use core from the filesystem rather than the JAR',
assign_new_value, true],
bundle_cmd: ['--bundle-cmd CMD', 'command to run for bundle', assign_new_value, 'bundle']
bundle_cmd: ['--bundle-cmd CMD', 'Command to run for bundle', assign_new_value, 'bundle'],
configuration: ['--config GEM_NAME', 'Load configuration for specified gem', assign_new_value, nil],
dir: ['--dir DIRECTORY', 'Set working directory', assign_new_value, Dir.pwd],
},
setup: {
help: ['-h', '--help', 'Show this message', assign_new_value, false],
@@ -170,27 +172,32 @@ class JRubyTruffleRunner

def initialize(argv = ARGV)
construct_default_options
load_gem_configuration
load_local_configuration
build_option_parsers

subcommand, *argv_after_global = @option_parsers[:global].order argv

if subcommand.nil?
print_options
help :global
end
help :global if @options[:global][:help]
Dir.chdir @options[:global][:dir] do
puts "pwd: #{Dir.pwd}" if verbose?

subcommand = subcommand.to_sym
load_gem_configuration
load_local_configuration

if subcommand.nil?
print_options
help :global
end
help :global if @options[:global][:help]

subcommand_option_parser = @option_parsers[subcommand] || raise("unknown subcommand: #{subcommand}")
argv_after_subcommand = subcommand_option_parser.order argv_after_global
subcommand = subcommand.to_sym

print_options
help subcommand if @options[subcommand][:help] && subcommand != :readme
subcommand_option_parser = @option_parsers[subcommand] || raise("unknown subcommand: #{subcommand}")
argv_after_subcommand = subcommand_option_parser.order argv_after_global

send "subcommand_#{subcommand}", argv_after_subcommand
print_options
help subcommand if @options[subcommand][:help] && subcommand != :readme

send "subcommand_#{subcommand}", argv_after_subcommand
end
end

def print_options
@@ -224,12 +231,11 @@ def build_option_parsers
end

def load_gem_configuration
candidates = Dir['*.gemspec'] # TODO pwd?
candidates = @options[:global][:configuration] ? [@options[:global][:configuration]] : Dir['*.gemspec']

if candidates.size == 1
gem_name, _ = candidates.first.split('.')
yaml_path = File.dirname(__FILE__) + "/gem_configurations/#{gem_name}.yaml"
File.exist? yaml_path
end

apply_yaml_to_configuration(yaml_path)
@@ -325,6 +331,7 @@ def subcommand_setup(rest)

@options[:setup][:file].each do |name, content|
puts "creating file: #{mock_path}/#{name}" if verbose?
FileUtils.mkpath File.dirname("#{mock_path}/#{name}")
File.write "#{mock_path}/#{name}", content
end

10 changes: 10 additions & 0 deletions spec/ruby/language/lambda_spec.rb
Original file line number Diff line number Diff line change
@@ -22,6 +22,16 @@ def create_lambda
-> () { }.lambda?.should be_true
end

it "has its own scope for local variables" do
l = -> arg {
var = arg
# this would override var if it was declared outside the lambda
l.call(arg-1) if arg > 0
var
}
l.call(1).should == 1
end

context "assigns no local variables" do
evaluate <<-ruby do
@a = -> { }
1 change: 0 additions & 1 deletion spec/tags/ruby/core/array/collect_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/tags/ruby/core/class/initialize_tags.txt

This file was deleted.

1 change: 0 additions & 1 deletion spec/tags/ruby/core/enumerable/max_by_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Enumerable#max_by when called with an argument n without a block returns an enumerator
fails:Enumerable#max_by when called with an argument n when n is nil returns the maximum element
1 change: 0 additions & 1 deletion spec/tags/ruby/core/enumerable/min_by_tags.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
fails:Enumerable#min_by when called with an argument n without a block returns an enumerator
fails:Enumerable#min_by when called with an argument n when n is nil returns the minimum element
1 change: 0 additions & 1 deletion spec/truffle/tags/language/return_tags.txt

This file was deleted.

77 changes: 77 additions & 0 deletions test/jruby/test_enumerator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
require 'test/unit'

class TestEnumerator < Test::Unit::TestCase

def test_stop_result_array
a = [1, 2]
enum = a.each
assert_equal(1, enum.next)
assert_equal(2, enum.next)
exc = assert_raise(StopIteration) { enum.next }
assert_equal(a, exc.result)

exc = assert_raise(StopIteration) { enum.next }
assert_equal(a, exc.result)
#assert_equal('iteration reached an end', exc.message)

#enum = a.map
#enum.next; enum.next
#exc = assert_raise(StopIteration) { enum.next }
#assert_equal([nil, nil], exc.result)
#assert_equal('iteration reached an end', exc.message)
end

def test_stop_result_obj
o = Object.new
def o.each
yield
yield 1
yield 1, 2
end
enum = o.to_enum
assert_equal(nil, enum.next)
assert_equal(1, enum.next)
assert_equal([1,2], enum.next)
exc = assert_raise(StopIteration) { enum.next }
assert_equal(nil, exc.result)
assert_equal('iteration reached an end', exc.message)

o = Object.new
def o.each
yield 1
yield 2
100
end
enum = o.to_enum
assert_equal(1, enum.next)
assert_equal(2, enum.next)
exc = assert_raise(StopIteration) { enum.next }
assert_equal(100, exc.result)
assert_equal('iteration reached an end', exc.message)
end

def test_stop_result_explicit
fib = Enumerator.new do |y|
a = b = 1
loop do
if a < 10 # [1, 1, 2, 3, 5, 8, 13]
y << a
a, b = b, a + b
else
raise StopIteration
end
end
:done
end
assert_equal [1, 1, 2, 3, 5, 8], fib.take(8)
fib.next
fib.next
fib.next
fib.next
fib.next
fib.next
exc = assert_raise(StopIteration) { fib.next }
assert_equal(:done, exc.result)
end

end
1 change: 1 addition & 0 deletions test/mri/excludes/TestEnumerable.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
exclude :test_slice_before, "needs investigation"
exclude :test_callcc, "Continuations are not supported"
exclude :test_zip, "needs investigation"
exclude :test_first, "works - but output is not exactly 'unexpected break' as in MRI"
1 change: 0 additions & 1 deletion test/mri/excludes/TestEnumerator.rb
Original file line number Diff line number Diff line change
@@ -11,5 +11,4 @@
exclude :test_peek_modify, "needs investigation"
exclude :test_peek_values, "needs investigation"
exclude :test_rewind_clear_feed, "needs investigation"
exclude :test_stop_result, "needs investigation"
exclude :test_with_index_large_offset, "needs investigation"
5 changes: 4 additions & 1 deletion test/mri/excludes/TestIO.rb
Original file line number Diff line number Diff line change
@@ -31,4 +31,7 @@
exclude :test_sysread_locktmp, "requires string locking we do not support"
exclude :test_warn, "needs investigation"
exclude :test_flush_in_finalizer1, "uses ObjectSpace"
exclude :test_flush_in_finalizer2, "uses ObjectSpace"
exclude :test_flush_in_finalizer2, "uses ObjectSpace"
exclude :test_reopen_stdio, "needs investigation"
exclude :test_set_stdout, "needs investigation"
exclude :test_threaded_flush, "needs investigation"
1 change: 1 addition & 0 deletions test/mri/excludes/TestKeywordArguments.rb
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
exclude :test_required_keyword_with_reserved, "needs investigation"
exclude :test_nonsymbol_key, "needs investigation"
3 changes: 3 additions & 0 deletions test/mri/excludes/TestMethod.rb
Original file line number Diff line number Diff line change
@@ -12,3 +12,6 @@
exclude :test_super_in_proc_from_define_method, "needs investigation"
exclude :test_super_method_removed, "finds super method when super method has been undef (#2155)"
exclude :test_visibility, "fails in compiler"
exclude :test_splat_long_array, "hangs when run"
exclude :test_insecure_method, "does not raise expected SecurityError"
exclude :test_to_proc_binding, "needs investigation"
20 changes: 11 additions & 9 deletions test/mri/excludes/TestProcess.rb
Original file line number Diff line number Diff line change
@@ -16,16 +16,18 @@
exclude :test_execopts_env, "needs investigation"
exclude :test_execopts_env_popen_string, "needs investigation"
exclude :test_execopts_env_popen_vector, "needs investigation"
exclude :test_execopts_env_single_word, "out of memory error"
exclude :test_execopts_exec, "needs investigation"
exclude :test_execopts_gid, "needs investigation"
exclude :test_execopts_open_chdir, "out of memory error"
exclude :test_execopts_pgroup, "needs investigation"
exclude :test_execopts_env_single_word, "Errno::ENOENT: No such file or directory - test_execopts_env_single_word.out"
exclude :test_execopts_exec, "TypeError: wrong exec option: #<IO:<STDOUT>>"
exclude :test_execopts_gid, "#<ArgumentError: wrong exec option symbol: gid>"
exclude :test_execopts_open_chdir, "sh: 1: cd: can't cd to foo"
exclude :test_execopts_pgroup, "#<TypeError: no implicit conversion of false into Integer>"
exclude :test_execopts_popen, "needs investigation"
exclude :test_execopts_preserve_env_on_exec_failure, "needs investigation"
exclude :test_execopts_redirect, "out of memory error"
exclude :test_execopts_redirect_dup2_child, "out of memory error"
exclude :test_execopts_redirect_nonascii_path, "out of memory error"
exclude :test_execopts_redirect_fd, "Errno::ENOENT: No such file or directory - out"
exclude :test_execopts_redirect_dup2_child, "Errno::ENOENT: No such file or directory - out"
exclude :test_execopts_redirect_nonascii_path, "needs investigation"
exclude :test_execopts_redirect_pipe, "Java::JavaLang::ArrayIndexOutOfBoundsException: -11"
exclude :test_execopts_redirect_symbol, "Errno::ENOENT: No such file or directory - out"
exclude :test_execopts_redirect_to_out_and_err, "out of memory error"
exclude :test_execopts_uid, "needs investigation"
exclude :test_execopts_umask, "unsupported"
@@ -34,7 +36,6 @@
exclude :test_fd_inheritance, "needs investigation"
exclude :test_gid_re_exchangeable_p, "unimplemented"
exclude :test_gid_sid_available?, "unimplemented"
exclude :test_no_curdir, "out of memory error"
exclude :test_popen_cloexec, "unsupported"
exclude :test_popen_noshell, "fails on linux (Travis)"
exclude :test_popen_wordsplit, "needs investigation"
@@ -51,3 +52,4 @@
exclude :test_system_wordsplit, "needs investigation"
exclude :test_uid_re_exchangeable_p, "unimplemented"
exclude :test_uid_sid_available?, "unimplemented"
exclude :test_wait_exception, "Interrupt expected but nothing was raised."
1 change: 1 addition & 0 deletions test/mri/excludes/TestRange.rb
Original file line number Diff line number Diff line change
@@ -2,3 +2,4 @@
exclude :test_bsearch_for_other_numerics, "needs investigation"
exclude :test_comparison_when_recursive, "needs investigation"
exclude :test_range_bsearch_for_floats, "needs investigation"
exclude :test_eqq_time, "TypeError: can't iterate from Time"
3 changes: 2 additions & 1 deletion test/mri/excludes/TestSymbol.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
exclude :test_ascii_incomat_inspect, "needs investigation"
exclude :test_inspect, "needs investigation"
exclude :test_inspect_dollar, "needs investigation"
#exclude :test_inspect_dollar, "needs investigation"
exclude :test_symbol_encoding, "needs investigation"
exclude :test_symbol_fstr_leak, "assert_no_memory_leak fails due an unexpected nil"
13 changes: 6 additions & 7 deletions test/mri/excludes/TestSyntax.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
exclude :test__END___cr, "needs investigation"
exclude :test_cmd_symbol_after_keyword, "needs investigation"
exclude :test_cmd_symbol_in_string, "needs investigation"
exclude :test_cmd_symbol_singleton_class, "needs investigation"
exclude :test_cmd_symbol_superclass, "needs investigation"
exclude :test_constant_reassignment_nested, "needs investigation"
exclude :test_constant_reassignment_toplevel, "needs investigation"
exclude :test_defined_empty_argument, "needs investigation"
exclude :test_do_block_after_blockcall_colon_no_arg, "NPE in parser"
exclude :test_do_block_after_blockcall_dot_no_arg, "NPE in parser"
exclude :test_do_block_in_call_args, "needs investigation"
exclude :test_do_block_in_cmdarg, "needs investigation"
exclude :test_do_block_in_cmdarg_begin, "needs investigation"
@@ -28,4 +22,9 @@
exclude :test_warn_balanced, "needs investigation"
exclude :test_warn_grouped_expression, "needs investigation"
exclude :test_warn_unreachable, "needs investigation"
exclude :test_warning_for_cr, "needs investigation"
exclude :test_warning_for_cr, "needs investigation"
exclude :test_bad_kwarg, "needs investigation"
exclude :test_do_block_in_lambda, "needs investigation"
exclude :test_invalid_next, "needs investigation"
exclude :test_null_range_cmdarg, "needs investigation"
exclude :test_too_big_nth_ref, "needs investigation"
3 changes: 3 additions & 0 deletions test/mri/excludes/TestTimeExtension.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
exclude :test_completion_with_different_timezone, "needs investigation"
exclude :test_encode_xmlschema, "needs investigation"
exclude :test_zone_0000, "needs investigation"
exclude :test_strptime_s_N, "1/1000000000000 subsec precision not supported"
exclude :test_iso8601_encode, "works except that we only have nano-sec precision up to 6-decimal values"
exclude :test_xmlschema_encode, "works except that we only have nano-sec precision up to 6-decimal values"
1 change: 1 addition & 0 deletions test/mri/excludes/TestTimeout.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
exclude :test_cannot_convert_into_time_interval, "needs investigation"
exclude :test_enumerator_next, "hangs"
exclude :test_skip_rescue, "needs investigation"
exclude :test_handle_interrupt, "since 2.2.2 - needs investigation"
1 change: 1 addition & 0 deletions test/mri/excludes_truffle/TestFloat.rb
Original file line number Diff line number Diff line change
@@ -14,3 +14,4 @@
exclude :test_strtod, "needs investigation"
exclude :test_to_s, "needs investigation"
exclude :test_truncate, "needs investigation"
exclude :test_hash_0, "-0.0.hash is not == 0.0.hash currently"
2 changes: 2 additions & 0 deletions test/mri/excludes_truffle/TestMethod.rb
Original file line number Diff line number Diff line change
@@ -41,3 +41,5 @@
exclude :test_unlinked_method_entry_in_method_object_bug, "needs investigation"
exclude :test_visibility, "needs investigation"
exclude :test_caller_top_level, "needs investigation"
exclude :test_insecure_method, "needs investigation"
exclude :test_to_proc_binding, "needs investigation"
1 change: 1 addition & 0 deletions test/mri/excludes_truffle/TestRange.rb
Original file line number Diff line number Diff line change
@@ -15,3 +15,4 @@
exclude :test_range_bsearch_for_floats, "needs investigation"
exclude :test_range_numeric_string, "needs investigation"
exclude :test_size, "needs investigation"
exclude :test_eqq_time, "needs investigation"
2 changes: 2 additions & 0 deletions test/mri/excludes_truffle/TestSymbol.rb
Original file line number Diff line number Diff line change
@@ -4,3 +4,5 @@
exclude :test_symbol_encoding, "needs investigation"
exclude :test_symbol_gc_1, "needs investigation"
exclude :test_to_proc, "needs investigation"
exclude :test_hash_redefinition, "hangs"
exclude :test_symbol_fstr_leak, "hangs"
6 changes: 6 additions & 0 deletions test/mri/excludes_truffle/TestSyntax.rb
Original file line number Diff line number Diff line change
@@ -51,3 +51,9 @@
exclude :test_brace_block_after_blockcall_colon_with_arg, "needs investigation"
exclude :test_newline_in_block_parameters, "needs investigation"
exclude :test_warn_unreachable, "needs investigation"
exclude :test_bad_kwarg, "needs investigation"
exclude :test_do_block_in_lambda, "needs investigation"
exclude :test_keyword_self_reference, "needs investigation"
exclude :test_keyword_empty_splat, "needs investigation"
exclude :test_null_range_cmdarg, "needs investigation"
exclude :test_too_big_nth_ref, "needs investigation"
9 changes: 9 additions & 0 deletions test/mri/excludes_truffle/TestTimeExtension.rb
Original file line number Diff line number Diff line change
@@ -24,3 +24,12 @@
exclude :test_xmlschema_fraction, "needs investigation"
exclude :test_xmlschema_leap_second, "needs investigation"
exclude :test_zone_0000, "needs investigation"
exclude :test_iso8601, "needs investigation"
exclude :test_iso8601_alias, "needs investigation"
exclude :test_iso8601_encode, "needs investigation"
exclude :test_iso8601_nsec, "needs investigation"
exclude :test_strptime_s_N, "needs investigation"
exclude :test_xmlschema_alias, "needs investigation"
exclude :test_xmlschema_encode, "needs investigation"
exclude :test_xmlschema_nsec, "needs investigation"
exclude :test_iso8601_leap_second, "needs investigation"
49 changes: 43 additions & 6 deletions test/mri/ruby/envutil.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# -*- coding: us-ascii -*-
require "open3"
require "timeout"
require "test/unit"
require_relative "find_executable"

module EnvUtil
@@ -69,8 +68,14 @@ def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr =
stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
else
signal = /mswin|mingw/ =~ RUBY_PLATFORM ? :KILL : :TERM
case pgroup = opt[:pgroup]
when 0, true
pgroup = -pid
when nil, false
pgroup = pid
end
begin
Process.kill signal, pid
Process.kill signal, pgroup
Timeout.timeout((reprieve unless signal == :KILL)) do
Process.wait(pid)
end
@@ -357,10 +362,8 @@ def assert_separately(args, file = nil, line = nil, src, ignore_stderr: nil, **o
line -= 5 # lines until src
src = <<eom
# -*- coding: #{src.encoding}; -*-
require #{__dir__.dump}'/envutil';include Test::Unit::Assertions
END {
puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}"
}
require #{__dir__.dump}'/envutil'; require 'test/unit'; include Test::Unit::Assertions
END { puts [Marshal.dump($!)].pack('m'), "assertions=\#{self._assertions}" }
#{src}
class Test::Unit::Runner
@@stop_auto_run = true
@@ -467,6 +470,8 @@ def assert_no_memory_leak(args, prepare, code, message=nil, limit: 1.5, rss: fal
next unless a > 0 and b > 0
assert_operator(a.fdiv(b), :<, limit, message(message) {"#{n}: #{b} => #{a}"})
end
rescue LoadError
skip
end

def assert_is_minus_zero(f)
@@ -520,6 +525,38 @@ def assert_pattern_list(pattern_list, actual, message=nil)
end
end

# threads should respond to shift method.
# Array can be used.
def assert_join_threads(threads, message = nil)
errs = []
values = []
while th = threads.shift
begin
values << th.value
rescue Exception
errs << [th, $!]
end
end
if !errs.empty?
msg = "exceptions on #{errs.length} threads:\n" +
errs.map {|t, err|
"#{t.inspect}:\n" +
err.backtrace.map.with_index {|line, i|
if i == 0
"#{line}: #{err.message} (#{err.class})"
else
"\tfrom #{line}"
end
}.join("\n")
}.join("\n---\n")
if message
msg = "#{message}\n#{msg}"
end
raise MiniTest::Assertion, msg
end
values
end

class << (AssertFile = Struct.new(:failure_message).new)
include Assertions
def assert_file_predicate(predicate, *args)
51 changes: 43 additions & 8 deletions test/mri/ruby/test_array.rb
Original file line number Diff line number Diff line change
@@ -806,7 +806,7 @@ def test_flatten!
end

def test_flatten_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
o = Object.new
def o.to_ary() callcc {|k| @cont = k; [1,2,3]} end
begin
@@ -820,7 +820,7 @@ def o.to_ary() callcc {|k| @cont = k; [1,2,3]} end
end

def test_permutation_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = [1,2,3]
@@ -837,7 +837,7 @@ def test_permutation_with_callcc
end

def test_product_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = [1,2,3]
@@ -854,7 +854,7 @@ def test_product_with_callcc
end

def test_combination_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = [1,2,3]
@@ -871,7 +871,7 @@ def test_combination_with_callcc
end

def test_repeated_permutation_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = [1,2,3]
@@ -888,7 +888,7 @@ def test_repeated_permutation_with_callcc
end

def test_repeated_combination_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = [1,2,3]
@@ -1366,7 +1366,7 @@ def test_sort!
end

def test_sort_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
n = 1000
cont = nil
ary = (1..100).to_a
@@ -2031,7 +2031,7 @@ def test_reject
end

def test_reject_with_callcc
respond_to?(:callcc, true) or require 'continuation'
need_continuation
bug9727 = '[ruby-dev:48101] [Bug #9727]'
cont = nil
a = [*1..10].reject do |i|
@@ -2496,4 +2496,39 @@ def test_shared_marking
skip e.message
end
end

sizeof_long = [0].pack("l!").size
sizeof_voidp = [""].pack("p").size
if sizeof_long < sizeof_voidp
ARY_MAX = (1<<(8*sizeof_long-1)) / sizeof_voidp - 1
Bug11235 = '[ruby-dev:49043] [Bug #11235]'

def test_push_over_ary_max
assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;")
a = Array.new(ARGV[0].to_i)
assert_raise(IndexError, ARGV[1]) {0x1000.times {a.push(1)}}
end;
end

def test_unshift_over_ary_max
assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;")
a = Array.new(ARGV[0].to_i)
assert_raise(IndexError, ARGV[1]) {0x1000.times {a.unshift(1)}}
end;
end

def test_splice_over_ary_max
assert_separately(['-', ARY_MAX.to_s, Bug11235], <<-"end;")
a = Array.new(ARGV[0].to_i)
assert_raise(IndexError, ARGV[1]) {a[0, 0] = Array.new(0x1000)}
end;
end
end

private
def need_continuation
unless respond_to?(:callcc, true)
EnvUtil.suppress_warning {require 'continuation'}
end
end
end
23 changes: 20 additions & 3 deletions test/mri/ruby/test_enum.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
require 'test/unit'
require 'continuation'
EnvUtil.suppress_warning {require 'continuation'}
require 'stringio'

class TestEnumerable < Test::Unit::TestCase
@@ -55,7 +55,8 @@ def test_grep

bug5801 = '[ruby-dev:45041]'
@empty.grep(//)
assert_nothing_raised(bug5801) {100.times {@empty.block.call}}
block = @empty.block
assert_nothing_raised(bug5801) {100.times {block.call}}

a = []
lambda = ->(x, i) {a << [x, i]}
@@ -173,6 +174,21 @@ def test_first
assert_equal(1, @obj.first)
assert_equal([1, 2, 3], @obj.first(3))
assert_nil(@empty.first)

bug5801 = '[ruby-dev:45041]'
assert_in_out_err([], <<-'end;', [], /unexpected break/)
empty = Object.new
class << empty
attr_reader :block
include Enumerable
def each(&block)
@block = block
self
end
end
empty.first
empty.block.call
end;
end

def test_sort
@@ -396,7 +412,8 @@ def test_take_while

bug5801 = '[ruby-dev:45040]'
@empty.take_while {true}
assert_nothing_raised(bug5801) {100.times {@empty.block.call}}
block = @empty.block
assert_nothing_raised(bug5801) {100.times {block.call}}
end

def test_drop
8 changes: 8 additions & 0 deletions test/mri/ruby/test_float.rb
Original file line number Diff line number Diff line change
@@ -673,4 +673,12 @@ def test_next_prev_float_zero
assert_equal(0.0, z)
assert_equal(-Float::INFINITY, 1.0/z)
end

def test_hash_0
bug10979 = '[ruby-core:68541] [Bug #10979]'
assert_equal(+0.0.hash, -0.0.hash)
assert_operator(+0.0, :eql?, -0.0)
h = {0.0 => bug10979}
assert_equal(bug10979, h[-0.0])
end
end
171 changes: 97 additions & 74 deletions test/mri/ruby/test_io.rb
Original file line number Diff line number Diff line change
@@ -584,11 +584,13 @@ def test_copy_stream_megacontent_nonblock
end
t1 = Thread.new { w1 << megacontent; w1.close }
t2 = Thread.new { r2.read }
ret = IO.copy_stream(r1, w2)
assert_equal(megacontent.bytesize, ret)
w2.close
t1.join
assert_equal(megacontent, t2.value)
t3 = Thread.new {
ret = IO.copy_stream(r1, w2)
assert_equal(megacontent.bytesize, ret)
w2.close
}
_, t2_value, _ = assert_join_threads([t1, t2, t3])
assert_equal(megacontent, t2_value)
}
}
}
@@ -601,11 +603,13 @@ def test_copy_stream_megacontent_pipe_to_file
with_pipe {|r2, w2|
t1 = Thread.new { w1 << megacontent; w1.close }
t2 = Thread.new { r2.read }
ret = IO.copy_stream(r1, w2)
assert_equal(megacontent.bytesize, ret)
w2.close
t1.join
assert_equal(megacontent, t2.value)
t3 = Thread.new {
ret = IO.copy_stream(r1, w2)
assert_equal(megacontent.bytesize, ret)
w2.close
}
_, t2_value, _ = assert_join_threads([t1, t2, t3])
assert_equal(megacontent, t2_value)
}
}
}
@@ -614,11 +618,14 @@ def test_copy_stream_megacontent_pipe_to_file
def test_copy_stream_megacontent_file_to_pipe
with_megasrc {|megasrc, megacontent|
with_pipe {|r, w|
t = Thread.new { r.read }
ret = IO.copy_stream(megasrc, w)
assert_equal(megacontent.bytesize, ret)
w.close
assert_equal(megacontent, t.value)
t1 = Thread.new { r.read }
t2 = Thread.new {
ret = IO.copy_stream(megasrc, w)
assert_equal(megacontent.bytesize, ret)
w.close
}
t1_value, _ = assert_join_threads([t1, t2])
assert_equal(megacontent, t1_value)
}
}
end
@@ -666,11 +673,13 @@ def test_copy_stream_socket1
def test_copy_stream_socket2
with_bigsrc {|bigsrc, bigcontent|
with_socketpair {|s1, s2|
t = Thread.new { s2.read }
ret = IO.copy_stream(bigsrc, s1)
assert_equal(bigcontent.bytesize, ret)
s1.close
result = t.value
t1 = Thread.new { s2.read }
t2 = Thread.new {
ret = IO.copy_stream(bigsrc, s1)
assert_equal(bigcontent.bytesize, ret)
s1.close
}
result, _ = assert_join_threads([t1, t2])
assert_equal(bigcontent, result)
}
}
@@ -679,11 +688,13 @@ def test_copy_stream_socket2
def test_copy_stream_socket3
with_bigsrc {|bigsrc, bigcontent|
with_socketpair {|s1, s2|
t = Thread.new { s2.read }
ret = IO.copy_stream(bigsrc, s1, 10000)
assert_equal(10000, ret)
s1.close
result = t.value
t1 = Thread.new { s2.read }
t2 = Thread.new {
ret = IO.copy_stream(bigsrc, s1, 10000)
assert_equal(10000, ret)
s1.close
}
result, _ = assert_join_threads([t1, t2])
assert_equal(bigcontent[0,10000], result)
}
}
@@ -694,12 +705,14 @@ def test_copy_stream_socket4
File.open(bigsrc) {|f|
assert_equal(0, f.pos)
with_socketpair {|s1, s2|
t = Thread.new { s2.read }
ret = IO.copy_stream(f, s1, nil, 100)
assert_equal(bigcontent.bytesize-100, ret)
assert_equal(0, f.pos)
s1.close
result = t.value
t1 = Thread.new { s2.read }
t2 = Thread.new {
ret = IO.copy_stream(f, s1, nil, 100)
assert_equal(bigcontent.bytesize-100, ret)
assert_equal(0, f.pos)
s1.close
}
result, _ = assert_join_threads([t1, t2])
assert_equal(bigcontent[100..-1], result)
}
}
@@ -712,12 +725,14 @@ def test_copy_stream_socket5
assert_equal(bigcontent[0,100], f.read(100))
assert_equal(100, f.pos)
with_socketpair {|s1, s2|
t = Thread.new { s2.read }
ret = IO.copy_stream(f, s1)
assert_equal(bigcontent.bytesize-100, ret)
assert_equal(bigcontent.length, f.pos)
s1.close
result = t.value
t1 = Thread.new { s2.read }
t2 = Thread.new {
ret = IO.copy_stream(f, s1)
assert_equal(bigcontent.bytesize-100, ret)
assert_equal(bigcontent.length, f.pos)
s1.close
}
result, _ = assert_join_threads([t1, t2])
assert_equal(bigcontent[100..-1], result)
}
}
@@ -735,11 +750,13 @@ def test_copy_stream_socket6
rescue Errno::EBADF
skip "nonblocking IO for pipe is not implemented"
end
t = Thread.new { s2.read }
ret = IO.copy_stream("megasrc", s1)
assert_equal(megacontent.bytesize, ret)
s1.close
result = t.value
t1 = Thread.new { s2.read }
t2 = Thread.new {
ret = IO.copy_stream("megasrc", s1)
assert_equal(megacontent.bytesize, ret)
s1.close
}
result, _ = assert_join_threads([t1, t2])
assert_equal(megacontent, result)
}
}
@@ -967,11 +984,12 @@ def test_copy_stream_strio_flush
w.write "zz"
src = StringIO.new("abcd")
IO.copy_stream(src, w)
t = Thread.new {
t1 = Thread.new {
w.close
}
assert_equal("zzabcd", r.read)
t.join
t2 = Thread.new { r.read }
_, result = assert_join_threads([t1, t2])
assert_equal("zzabcd", result)
}
end

@@ -1241,7 +1259,6 @@ def test_read_buffer_error
end

def test_write_nonblock
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
pipe(proc do |w|
w.write_nonblock(1)
w.close
@@ -1251,7 +1268,6 @@ def test_write_nonblock
end

def test_read_nonblock_with_not_empty_buffer
skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
w.write "foob"
w.close
@@ -1261,7 +1277,6 @@ def test_read_nonblock_with_not_empty_buffer
end

def test_write_nonblock_simple_no_exceptions
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
pipe(proc do |w|
w.write_nonblock('1', exception: false)
w.close
@@ -1272,7 +1287,6 @@ def test_write_nonblock_simple_no_exceptions

def test_read_nonblock_error
return if !have_nonblock?
skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
begin
r.read_nonblock 4096
@@ -1292,7 +1306,6 @@ def test_read_nonblock_error

def test_read_nonblock_no_exceptions
return if !have_nonblock?
skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, exception: false)
w.puts "HI!"
@@ -1304,7 +1317,6 @@ def test_read_nonblock_no_exceptions

def test_read_nonblock_with_buffer_no_exceptions
return if !have_nonblock?
skip "IO#read_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
assert_equal :wait_readable, r.read_nonblock(4096, "", exception: false)
w.puts "HI!"
@@ -1319,7 +1331,6 @@ def test_read_nonblock_with_buffer_no_exceptions

def test_write_nonblock_error
return if !have_nonblock?
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
begin
loop {
@@ -1333,7 +1344,6 @@ def test_write_nonblock_error

def test_write_nonblock_no_exceptions
return if !have_nonblock?
skip "IO#write_nonblock is not supported on file/pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe {|r, w|
loop {
ret = w.write_nonblock("a"*100000, exception: false)
@@ -2041,6 +2051,15 @@ def test_reopen_inherit
}
end

def test_reopen_stdio
mkcdtmpdir {
fname = 'bug11319'
File.write(fname, 'hello')
system(EnvUtil.rubybin, '-e', "STDOUT.reopen('#{fname}', 'w+')")
assert_equal('', File.read(fname))
}
end

def test_reopen_mode
feature7067 = '[ruby-core:47694]'
make_tempfile {|t|
@@ -2259,6 +2278,14 @@ def test_set_stdout
assert_raise(TypeError) { $> = Object.new }

assert_in_out_err([], "$> = $stderr\nputs 'foo'", [], %w(foo))

assert_separately(%w[-Eutf-8], <<-"end;") # do
alias $\u{6a19 6e96 51fa 529b} $stdout
x = eval("class X\u{307b 3052}; self; end".encode("euc-jp"))
assert_raise_with_message(TypeError, /\\$\u{6a19 6e96 51fa 529b} must.*, X\u{307b 3052} given/) do
$\u{6a19 6e96 51fa 529b} = x.new
end
end;
end

def test_initialize
@@ -2625,7 +2652,6 @@ def test_fcntl_dupfd
end

def test_cross_thread_close_fd
skip "cross thread close causes hung-up if pipe." if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
with_pipe do |r,w|
read_thread = Thread.new do
begin
@@ -2747,7 +2773,7 @@ def test_race_between_read
threads << Thread.new {write_file.print(i)}
threads << Thread.new {read_file.read}
end
threads.each {|t| t.join}
assert_join_threads(threads)
assert(true, "[ruby-core:37197]")
ensure
read_file.close
@@ -2914,33 +2940,30 @@ def test_sysread_locktmp
end

def test_readpartial_locktmp
skip "nonblocking mode is not supported for pipe on this platform" if /mswin|bccwin|mingw/ =~ RUBY_PLATFORM
bug6099 = '[ruby-dev:45297]'
buf = " " * 100
data = "a" * 100
th = nil
with_pipe do |r,w|
begin
r.nonblock = true
th = Thread.new {r.readpartial(100, buf)}
r.nonblock = true
th = Thread.new {r.readpartial(100, buf)}

Thread.pass until th.stop?
Thread.pass until th.stop?

assert_equal 100, buf.bytesize
assert_equal 100, buf.bytesize

begin
buf.replace("")
rescue RuntimeError => e
assert_match(/can't modify string; temporarily locked/, e.message)
Thread.pass
end until buf.empty?
begin
buf.replace("")
rescue RuntimeError => e
assert_match(/can't modify string; temporarily locked/, e.message)
Thread.pass
end until buf.empty?

assert_empty(buf, bug6099)
assert_predicate(th, :alive?)
w.write(data)
Thread.pass while th.alive?
th.join
end
assert_empty(buf, bug6099)
assert_predicate(th, :alive?)
w.write(data)
Thread.pass while th.alive?
th.join
end
assert_equal(data, buf, bug6099)
end
@@ -3038,7 +3061,7 @@ def test_io_select_with_many_files
}
IO.select(tempfiles)
}, bug8080
}, bug8080, timeout: 30
end

def test_read_32bit_boundary
53 changes: 50 additions & 3 deletions test/mri/ruby/test_keyword.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
require 'test/unit'
require_relative 'envutil'

class TestKeywordArguments < Test::Unit::TestCase
def f1(str: "foo", num: 424242)
@@ -311,11 +310,16 @@ def test_required_keyword
feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument'
o = Object.new
assert_nothing_raised(SyntaxError, feature7701) do
eval("def o.foo(a:) a; end")
eval("def o.foo(a:) a; end", nil, "xyzzy")
eval("def o.bar(a:,**b) [a, b]; end")
end
assert_raise_with_message(ArgumentError, /missing keyword/, feature7701) {o.foo}
assert_raise_with_message(ArgumentError, /unknown keyword/, feature7701) {o.foo(a:0, b:1)}
begin
o.foo(a: 0, b: 1)
rescue => e
assert_equal('xyzzy', e.backtrace_locations[0].path)
end
assert_equal(42, o.foo(a: 42), feature7701)
assert_equal([[:keyreq, :a]], o.method(:foo).parameters, feature7701)

@@ -363,10 +367,16 @@ def test_required_keyword_with_reserved
def test_block_required_keyword
feature7701 = '[ruby-core:51454] [Feature #7701] required keyword argument'
b = assert_nothing_raised(SyntaxError, feature7701) do
break eval("proc {|a:| a}", nil, __FILE__, __LINE__)
break eval("proc {|a:| a}", nil, 'xyzzy', __LINE__)
end
assert_raise_with_message(ArgumentError, /missing keyword/, feature7701) {b.call}
assert_raise_with_message(ArgumentError, /unknown keyword/, feature7701) {b.call(a:0, b:1)}
begin
b.call(a: 0, b: 1)
rescue => e
assert_equal('xyzzy', e.backtrace_locations[0].path)
end

assert_equal(42, b.call(a: 42), feature7701)
assert_equal([[:keyreq, :a]], b.parameters, feature7701)

@@ -529,4 +539,41 @@ def foo
o.foo {raise "unreachable"}
}
end

def test_super_with_anon_restkeywords
bug10659 = '[ruby-core:67157] [Bug #10659]'

foo = Class.new do
def foo(**h)
h
end
end

class << (obj = foo.new)
def foo(bar: "bar", **)
super
end
end

assert_nothing_raised(TypeError, bug10659) {
assert_equal({:bar => "bar"}, obj.foo, bug10659)
}
end

def m(a) yield a end

def test_nonsymbol_key
result = m(["a" => 10]) { |a = nil, **b| [a, b] }
assert_equal([{"a" => 10}, {}], result)
end

def method_for_test_to_hash_call_during_setup_complex_parameters k1:, k2:, **rest_kw
[k1, k2, rest_kw]
end

def test_to_hash_call_during_setup_complex_parameters
sym = "sym_#{Time.now}".to_sym
h = method_for_test_to_hash_call_during_setup_complex_parameters k1: "foo", k2: "bar", sym => "baz"
assert_equal ["foo", "bar", {sym => "baz"}], h, '[Bug #11027]'
end
end
37 changes: 36 additions & 1 deletion test/mri/ruby/test_method.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- coding: us-ascii -*-
require 'test/unit'
require_relative 'envutil'

class TestMethod < Test::Unit::TestCase
def setup
@@ -856,4 +855,40 @@ def test_super_method_removed
m = assert_nothing_raised(NameError, Feature9781) {break m.super_method}
assert_nil(m, Feature9781)
end

def rest_parameter(*rest)
rest
end

def test_splat_long_array
n = 10_000_000
assert_equal n , rest_parameter(*(1..n)).size, '[Feature #10440]'
end

def test_insecure_method
m = "\u{5371 967a}"
c = Class.new do
proc {$SAFE=3;def foo;end}.call
alias_method m, "foo"
eval "def bar; #{m}; end"
end
obj = c.new
assert_raise_with_message(SecurityError, /#{m}/) do
obj.bar
end
end

def test_to_proc_binding
bug11012 = '[ruby-core:68673] [Bug #11012]'
class << (obj = Object.new)
src = 1000.times.map {|i|"v#{i} = nil"}.join("\n")
eval("def foo()\n""#{src}\n""end")
end

b = obj.method(:foo).to_proc.binding
b.local_variables.each_with_index {|n, i|
b.local_variable_set(n, i)
}
assert_equal([998, 999], %w[v998 v999].map {|n| b.local_variable_get(n)}, bug11012)
end
end
263 changes: 151 additions & 112 deletions test/mri/ruby/test_process.rb
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
require 'tempfile'
require 'timeout'
require 'io/wait'
require_relative 'envutil'
require 'rbconfig'

class TestProcess < Test::Unit::TestCase
@@ -462,7 +461,7 @@ def with_pipes(n)
SORT = [RUBY, '-e', "puts ARGF.readlines.sort"]
CAT = [RUBY, '-e', "IO.copy_stream STDIN, STDOUT"]

def test_execopts_redirect
def test_execopts_redirect_fd
with_tmpchdir {|d|
Process.wait Process.spawn(*ECHO["a"], STDOUT=>["out", File::WRONLY|File::CREAT|File::TRUNC, 0644])
assert_equal("a", File.read("out").chomp)
@@ -534,76 +533,82 @@ def test_execopts_redirect
assert_equal("bb\naa\n", File.read("out"))
system(*SORT, STDIN=>["out"], STDOUT=>"out2")
assert_equal("aa\nbb\n", File.read("out2"))
}
end

with_pipe {|r1, w1|
with_pipe {|r2, w2|
opts = {STDIN=>r1, STDOUT=>w2}
opts.merge(w1=>:close, r2=>:close) unless windows?
pid = spawn(*SORT, opts)
r1.close
w2.close
w1.puts "c"
w1.puts "a"
w1.puts "b"
w1.close
assert_equal("a\nb\nc\n", r2.read)
r2.close
Process.wait(pid)
}
def test_execopts_redirect_pipe
with_pipe {|r1, w1|
with_pipe {|r2, w2|
opts = {STDIN=>r1, STDOUT=>w2}
opts.merge(w1=>:close, r2=>:close) unless windows?
pid = spawn(*SORT, opts)
r1.close
w2.close
w1.puts "c"
w1.puts "a"
w1.puts "b"
w1.close
assert_equal("a\nb\nc\n", r2.read)
r2.close
Process.wait(pid)
}
}

unless windows?
# passing non-stdio fds is not supported on Windows
with_pipes(5) {|pipes|
ios = pipes.flatten
h = {}
ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] }
h2 = h.invert
_rios = pipes.map {|r, w| r }
wios = pipes.map {|r, w| w }
child_wfds = wios.map {|w| h2[w].fileno }
pid = spawn(RUBY, "-e",
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
pipes.each {|r, w|
assert_equal("#{h2[w].fileno}\n", r.gets)
}
Process.wait pid;
unless windows?
# passing non-stdio fds is not supported on Windows
with_pipes(5) {|pipes|
ios = pipes.flatten
h = {}
ios.length.times {|i| h[ios[i]] = ios[(i-1)%ios.length] }
h2 = h.invert
_rios = pipes.map {|r, w| r }
wios = pipes.map {|r, w| w }
child_wfds = wios.map {|w| h2[w].fileno }
pid = spawn(RUBY, "-e",
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
pipes.each {|r, w|
assert_equal("#{h2[w].fileno}\n", r.gets)
}
Process.wait pid;
}

with_pipes(5) {|pipes|
ios = pipes.flatten
h = {}
ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] }
h2 = h.invert
_rios = pipes.map {|r, w| r }
wios = pipes.map {|r, w| w }
child_wfds = wios.map {|w| h2[w].fileno }
pid = spawn(RUBY, "-e",
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
pipes.each {|r, w|
assert_equal("#{h2[w].fileno}\n", r.gets)
}
Process.wait pid
with_pipes(5) {|pipes|
ios = pipes.flatten
h = {}
ios.length.times {|i| h[ios[i]] = ios[(i+1)%ios.length] }
h2 = h.invert
_rios = pipes.map {|r, w| r }
wios = pipes.map {|r, w| w }
child_wfds = wios.map {|w| h2[w].fileno }
pid = spawn(RUBY, "-e",
"[#{child_wfds.join(',')}].each {|fd| IO.new(fd, 'w').puts fd }", h)
pipes.each {|r, w|
assert_equal("#{h2[w].fileno}\n", r.gets)
}
Process.wait pid
}

closed_fd = nil
with_pipes(5) {|pipes|
io = pipes.last.last
closed_fd = io.fileno
}
assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) }

with_pipe {|r, w|
if w.respond_to?(:"close_on_exec=")
w.close_on_exec = true
pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w)
w.close
assert_equal("a", r.read)
Process.wait pid
end
}
end
closed_fd = nil
with_pipes(5) {|pipes|
io = pipes.last.last
closed_fd = io.fileno
}
assert_raise(Errno::EBADF) { Process.wait spawn(*TRUECOMMAND, closed_fd=>closed_fd) }

with_pipe {|r, w|
if w.respond_to?(:"close_on_exec=")
w.close_on_exec = true
pid = spawn(RUBY, "-e", "IO.new(#{w.fileno}, 'w').print 'a'", w=>w)
w.close
assert_equal("a", r.read)
Process.wait pid
end
}
end
end

def test_execopts_redirect_symbol
with_tmpchdir {|d|
system(*ECHO["funya"], :out=>"out")
assert_equal("funya\n", File.read("out"))
system(RUBY, '-e', 'STDOUT.reopen(STDERR); puts "henya"', :err=>"out")
@@ -1294,6 +1299,29 @@ def test_waitall
end
end

def test_wait_exception
bug11340 = '[ruby-dev:49176] [Bug #11340]'
t0 = t1 = nil
IO.popen([RUBY, '-e', 'puts;STDOUT.flush;Thread.start{gets;exit};sleep(3)'], 'r+') do |f|
pid = f.pid
f.gets
t0 = Time.now
th = Thread.start(Thread.current) do |main|
Thread.pass until main.stop?
main.raise Interrupt
end
begin
assert_raise(Interrupt) {Process.wait(pid)}
ensure
th.kill.join
end
t1 = Time.now
f.puts
end
assert_operator(t1 - t0, :<, 3,
->{"#{bug11340}: #{t1-t0} seconds to interrupt Process.wait"})
end

def test_abort
with_tmpchdir do
s = run_in_child("abort")
@@ -1919,59 +1947,70 @@ def test_clock_getres_MACH_ABSOLUTE_TIME_BASED_CLOCK_MONOTONIC
end

def test_deadlock_by_signal_at_forking
GC.start # reduce garbage
buf = ''
ruby = EnvUtil.rubybin
er, ew = IO.pipe
unless runner = IO.popen("-".freeze)
er.close
status = true
GC.disable # avoid triggering CoW after forks
assert_separately([], <<-INPUT, timeout: 60)
require 'io/wait'
begin
$stderr.reopen($stdout)
trap(:QUIT) {}
parent = $$
100.times do |i|
pid = fork {Process.kill(:QUIT, parent)}
IO.popen(ruby, 'r+'.freeze){}
Process.wait(pid)
$stdout.puts
$stdout.flush
GC.start # reduce garbage
buf = ''
ruby = EnvUtil.rubybin
er, ew = IO.pipe
unless runner = IO.popen("-".freeze)
er.close
status = true
GC.disable # avoid triggering CoW after forks
begin
$stderr.reopen($stdout)
trap(:QUIT) {}
parent = $$
100.times do |i|
pid = fork {Process.kill(:QUIT, parent)}
IO.popen(ruby, 'r+'.freeze){}
Process.wait(pid)
$stdout.puts
$stdout.flush
end
ensure
if $!
ew.puts([Marshal.dump($!)].pack("m0"))
status = false
end
ew.close
exit!(status)
end
end
ew.close
begin
loop do
runner.wait_readable(5)
runner.read_nonblock(100, buf)
end
rescue EOFError => e
_, status = Process.wait2(runner.pid)
rescue IO::WaitReadable => e
Process.kill(:INT, runner.pid)
exc = Marshal.load(er.read.unpack("m")[0])
if exc.kind_of? Interrupt
# Don't raise Interrupt. It aborts test-all.
flunk "timeout"
else
raise exc
end
end
assert_predicate(status, :success?)
ensure
if $!
ew.puts([Marshal.dump($!)].pack("m0"))
status = false
er.close unless er.closed?
ew.close unless ew.closed?
if runner
begin
Process.kill(:TERM, runner.pid)
sleep 1
Process.kill(:KILL, runner.pid)
rescue Errno::ESRCH
end
runner.close
end
ew.close
exit!(status)
end
end
ew.close
begin
loop do
runner.wait_readable(5)
runner.read_nonblock(100, buf)
end
rescue EOFError => e
_, status = Process.wait2(runner.pid)
rescue IO::WaitReadable => e
Process.kill(:INT, runner.pid)
raise Marshal.load(er.read.unpack("m")[0])
end
assert_predicate(status, :success?)
ensure
er.close unless er.closed?
ew.close unless ew.closed?
if runner
begin
Process.kill(:TERM, runner.pid)
sleep 1
Process.kill(:KILL, runner.pid)
rescue Errno::ESRCH
end
runner.close
end
INPUT
end if defined?(fork)

def test_process_detach
10 changes: 8 additions & 2 deletions test/mri/ruby/test_range.rb
Original file line number Diff line number Diff line change
@@ -2,7 +2,6 @@
require 'delegate'
require 'timeout'
require 'bigdecimal'
require_relative 'envutil'

class TestRange < Test::Unit::TestCase
def test_range_string
@@ -69,7 +68,6 @@ def test_max
assert_equal(2, (1..2).max)
assert_equal(nil, (2..1).max)
assert_equal(1, (1...2).max)
assert_equal(18446744073709551615, (0...2**64).max)

assert_equal(2.0, (1.0..2.0).max)
assert_equal(nil, (2.0..1.0).max)
@@ -285,6 +283,14 @@ def test_eqq
assert_not_operator(0..10, :===, 11)
end

def test_eqq_time
bug11113 = '[ruby-core:69052] [Bug #11113]'
t = Time.now
assert_nothing_raised(TypeError, bug11113) {
assert_operator(t..(t+10), :===, t+5)
}
end

def test_include
assert_include("a".."z", "c")
assert_not_include("a".."z", "5")
56 changes: 56 additions & 0 deletions test/mri/ruby/test_symbol.rb
Original file line number Diff line number Diff line change
@@ -199,6 +199,35 @@ def test_singleton_method
assert_raise(TypeError) { a = :foo; def a.foo; end }
end

SymbolsForEval = [
:foo,
"dynsym_#{Random.rand(10000)}_#{Time.now}".to_sym
]

def test_instance_eval
bug11086 = '[ruby-core:68961] [Bug #11086]'
SymbolsForEval.each do |sym|
assert_nothing_raised(TypeError, sym, bug11086) {
sym.instance_eval {}
}
assert_raise(TypeError, sym, bug11086) {
sym.instance_eval {def foo; end}
}
end
end

def test_instance_exec
bug11086 = '[ruby-core:68961] [Bug #11086]'
SymbolsForEval.each do |sym|
assert_nothing_raised(TypeError, sym, bug11086) {
sym.instance_exec {}
}
assert_raise(TypeError, sym, bug11086) {
sym.instance_exec {def foo; end}
}
end
end

def test_frozen_symbol
assert_equal(true, :foo.frozen?)
assert_equal(true, :each.frozen?)
@@ -230,4 +259,31 @@ class << (obj = Object.new)
end
assert_nothing_raised(NoMethodError, bug10259) {obj.send("unagi=".intern, 1)}
end

def test_symbol_fstr_leak
bug10686 = '[ruby-core:67268] [Bug #10686]'
x = 0
assert_no_memory_leak([], '', <<-"end;", bug10686, limit: 1.65)
200_000.times { |i| i.to_s.to_sym }
end;
end

def test_hash_redefinition
assert_separately([], <<-'end;')
bug11035 = '[ruby-core:68767] [Bug #11035]'
class Symbol
def hash
raise
end
end
h = {}
assert_nothing_raised(RuntimeError, bug11035) {
h[:foo] = 1
}
assert_nothing_raised(RuntimeError, bug11035) {
h['bar'.to_sym] = 2
}
end;
end
end
Loading

0 comments on commit 877da98

Please sign in to comment.