Skip to content
Permalink

Comparing changes

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

Open a pull request

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

Commits on Feb 8, 2018

  1. avoid coverting ENV key to Java string for case (insensitive) comparison

    ... also properly handle value freeze - user value should not change
    kares committed Feb 8, 2018

    Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    nomadium Miguel Landaeta
    Copy the full SHA
    cb4aa77 View commit details
  2. Verified

    This commit was signed with the committer’s verified signature. The key has expired.
    nomadium Miguel Landaeta
    Copy the full SHA
    b2ef99d View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    dea1d09 View commit details
  4. Copy the full SHA
    ec7f7b7 View commit details
  5. Copy the full SHA
    38c234f View commit details
  6. Copy the full SHA
    9ed5bc6 View commit details
28 changes: 21 additions & 7 deletions core/src/main/java/org/jruby/RubyBignum.java
Original file line number Diff line number Diff line change
@@ -1105,13 +1105,13 @@ public IRubyObject fdivDouble(ThreadContext context, IRubyObject y) {

dx = getDoubleValue();
if (y instanceof RubyFixnum) {
dy = (double)fix2long(y);
if (Double.isInfinite(dx))
return fdivInt(context, RubyBignum.newBignum(context.runtime, fix2long(y)));
long ly = ((RubyFixnum) y).getLongValue();
if (Double.isInfinite(dx)) {
return fdivInt(context.runtime, BigDecimal.valueOf(ly));
}
dy = (double) ly;
} else if (y instanceof RubyBignum) {
dy = RubyBignum.big2dbl((RubyBignum) y);
if (Double.isInfinite(dx) || Double.isInfinite(dy))
return fdivInt(context, (RubyBignum) y);
return fdivDouble(context, (RubyBignum) y);
} else if (y instanceof RubyFloat) {
dy = ((RubyFloat) y).getDoubleValue();
if (Double.isNaN(dy)) {
@@ -1126,9 +1126,23 @@ public IRubyObject fdivDouble(ThreadContext context, IRubyObject y) {
return context.runtime.newFloat(dx / dy);
}

final RubyFloat fdivDouble(ThreadContext context, RubyBignum y) {
double dx = getDoubleValue();
double dy = RubyBignum.big2dbl(y);
if (Double.isInfinite(dx) || Double.isInfinite(dy)) {
return (RubyFloat) fdivInt(context, y);
}

return context.runtime.newFloat(dx / dy);
}

// MRI: big_fdiv_int and big_fdiv
public IRubyObject fdivInt(ThreadContext context, RubyBignum y) {
return context.runtime.newFloat(new BigDecimal(value).divide(new BigDecimal(y.getValue())).doubleValue());
return fdivInt(context.runtime, new BigDecimal(y.value));
}

private RubyFloat fdivInt(final Ruby runtime, BigDecimal y) {
return runtime.newFloat(new BigDecimal(value).divide(y).doubleValue());
}

// MRI: big_fdiv_float
16 changes: 11 additions & 5 deletions core/src/main/java/org/jruby/RubyFixnum.java
Original file line number Diff line number Diff line change
@@ -1271,6 +1271,11 @@ public final boolean isZero() {
return value == 0;
}

@Override
final boolean isOne() {
return value == 1;
}

@Override
public IRubyObject succ(ThreadContext context) {
return ((RubyFixnum) this).op_plus_one(context);
@@ -1326,13 +1331,14 @@ public RubyInteger convertToInteger(String method) {
public IRubyObject fdivDouble(ThreadContext context, IRubyObject y) {
if (y instanceof RubyFixnum) {
return context.runtime.newFloat(((double) value) / ((double) fix2long(y)));
} else if (y instanceof RubyBignum) {
return RubyBignum.newBignum(context.runtime, value).fdivDouble(context, y);
} else if (y instanceof RubyFloat) {
}
if (y instanceof RubyBignum) {
return RubyBignum.newBignum(context.runtime, value).fdivDouble(context, (RubyBignum) y);
}
if (y instanceof RubyFloat) {
return context.runtime.newFloat(((double) value) / ((RubyFloat) y).getDoubleValue());
} else {
return coerceBin(context, sites(context).fdiv, y);
}
return coerceBin(context, sites(context).fdiv, y);
}

@Override
16 changes: 12 additions & 4 deletions core/src/main/java/org/jruby/RubyGlobal.java
Original file line number Diff line number Diff line change
@@ -536,7 +536,7 @@ private RubyString getCorrectKey(final RubyString key) {
final RubyArray keys = super.keys();
for (int i = 0; i < keys.size(); i++) {
RubyString candidateKey = keys.eltInternal(i).convertToString();
if (Numeric.f_zero_p(context, candidateKey.casecmp(context, key))) {
if (equalIgnoreCase(context, candidateKey, key)) {
actualKey = candidateKey;
break;
}
@@ -545,24 +545,32 @@ private RubyString getCorrectKey(final RubyString key) {
return actualKey;
}

private static final ByteList PATH_BYTES = new ByteList(new byte[] {'P','A','T','H'}, USASCIIEncoding.INSTANCE, false);

private static RubyString normalizeEnvString(ThreadContext context, RubyString key, RubyString value) {
final Ruby runtime = context.runtime;

final RubyString valueStr;
RubyString valueStr;

// Ensure PATH is encoded like filesystem
if (Platform.IS_WINDOWS ?
key.toString().equalsIgnoreCase("PATH") :
key.toString().equals("PATH")) {
equalIgnoreCase(context, key, RubyString.newString(context.runtime, PATH_BYTES)) :
key.getByteList().equal(PATH_BYTES)) {
Encoding enc = runtime.getEncodingService().getFileSystemEncoding();
valueStr = EncodingUtils.strConvEnc(context, value, value.getEncoding(), enc);
if (value == valueStr) valueStr = (RubyString) value.dup();
} else {
valueStr = RubyString.newString(runtime, value.toString(), runtime.getEncodingService().getLocaleEncoding());
}

valueStr.setFrozen(true);
return valueStr;
}

private static boolean equalIgnoreCase(ThreadContext context, final RubyString str1, final RubyString str2) {
return ((RubyFixnum) str1.casecmp(context, str2)).isZero();
}

}

private static class ReadOnlySystemPropertiesHash extends StringOnlyRubyHash {
27 changes: 14 additions & 13 deletions core/src/main/java/org/jruby/RubyInteger.java
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@
import org.jruby.util.ByteList;
import org.jruby.util.io.EncodingUtils;

import java.math.BigInteger;
import java.math.RoundingMode;

import static org.jruby.RubyEnumerator.enumeratorizeWithSize;
@@ -472,7 +473,7 @@ public IRubyObject round(ThreadContext context, int ndigits) {
/*
* MRI: rb_int_round
*/
protected IRubyObject roundShared(ThreadContext context, int ndigits, RoundingMode roundingMode) {
private RubyNumeric roundShared(ThreadContext context, int ndigits, RoundingMode roundingMode) {
Ruby runtime = context.runtime;

RubyNumeric f, h, n, r;
@@ -582,20 +583,16 @@ public IRubyObject rationalize(ThreadContext context, IRubyObject[] args) {

@JRubyMethod(name = "odd?")
public RubyBoolean odd_p(ThreadContext context) {
Ruby runtime = context.runtime;
if (sites(context).op_mod.call(context, this, this, RubyFixnum.two(runtime)) != RubyFixnum.zero(runtime)) {
return runtime.getTrue();
}
return runtime.getFalse();
return (op_mod_two(context, this) != 0) ? context.tru : context.fals;
}

@JRubyMethod(name = "even?")
public RubyBoolean even_p(ThreadContext context) {
Ruby runtime = context.runtime;
if (sites(context).op_mod.call(context, this, this, RubyFixnum.two(runtime)) == RubyFixnum.zero(runtime)) {
return runtime.getTrue();
}
return runtime.getFalse();
return (op_mod_two(context, this) == 0) ? context.tru : context.fals;
}

private static long op_mod_two(ThreadContext context, RubyInteger self) {
return ((RubyInteger) sites(context).op_mod.call(context, self, self, RubyFixnum.two(context.runtime))).getLongValue();
}

@JRubyMethod(name = "pred")
@@ -615,9 +612,9 @@ public IRubyObject gcd(ThreadContext context, IRubyObject other) {
@Override
public IRubyObject fdiv(ThreadContext context, IRubyObject y) {
RubyInteger x = this;
if (y instanceof RubyInteger && !((RubyInteger) y).zero_p(context).isTrue()) {
if (y instanceof RubyInteger && !((RubyInteger) y).isZero()) {
IRubyObject gcd = gcd(context, y);
if (!((RubyInteger) gcd).zero_p(context).isTrue()) {
if (!((RubyInteger) gcd).isZero()) {
x = (RubyInteger) div(context, gcd);
y = ((RubyInteger) y).div(context, gcd);
}
@@ -780,6 +777,10 @@ public IRubyObject magnitude(ThreadContext context) {
@JRubyMethod(name = "bit_length")
public abstract IRubyObject bit_length(ThreadContext context);

boolean isOne() {
return getBigIntegerValue().equals(BigInteger.ONE);
}

public IRubyObject op_gt(ThreadContext context, IRubyObject other) {
return RubyComparable.op_gt(context, this, other);
}
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/RubyString.java
Original file line number Diff line number Diff line change
@@ -1280,7 +1280,7 @@ public boolean equals(Object other) {
return (other instanceof RubyString) && equals((RubyString) other);
}

private boolean equals(RubyString other) {
final boolean equals(RubyString other) {
return ((RubyString) other).value.equal(value);
}

146 changes: 54 additions & 92 deletions core/src/main/java/org/jruby/RubyTime.java
Original file line number Diff line number Diff line change
@@ -113,16 +113,6 @@ public class RubyTime extends RubyObject {

private boolean isTzRelative = false; // true if and only if #new is called with a numeric offset (e.g., "+03:00")

/* JRUBY-3560
* joda-time disallows use of three-letter time zone IDs.
* Since MRI accepts these values, we need to translate them.
*/
private static final Map<String, String> LONG_TZNAME = Helpers.map(
"MET", "CET", // JRUBY-2759
"ROC", "Asia/Taipei", // Republic of China
"WET", "Europe/Lisbon" // Western European Time
);

/* Some TZ values need to be overriden for Time#zone
*/
private static final Map<String, String> SHORT_STD_TZNAME = Helpers.map(
@@ -175,8 +165,6 @@ public static DateTimeZone getTimeZoneFromTZString(Ruby runtime, String zone) {
}

private static DateTimeZone parseTZString(Ruby runtime, String zone) {
String upZone = zone.toUpperCase();

Matcher tzMatcher = TZ_PATTERN.matcher(zone);
if (tzMatcher.matches()) {
String zoneName = tzMatcher.group(1);
@@ -185,30 +173,34 @@ private static DateTimeZone parseTZString(Ruby runtime, String zone) {
String minutes = tzMatcher.group(4);
String seconds= tzMatcher.group(5);

if (zoneName == null) {
zoneName = "";
}
if (zoneName == null) zoneName = "";

// Sign is reversed in legacy TZ notation
return getTimeZoneFromHHMM(runtime, zoneName, sign.equals("-"), hours, minutes, seconds);
} else {
if (LONG_TZNAME.containsKey(upZone)) {
zone = LONG_TZNAME.get(upZone);
} else if (upZone.equals("UTC") || upZone.equals("GMT")) {
}

if (zone.length() == 3) {
switch (zone.toUpperCase()) {
// joda-time disallows use of three-letter time zone IDs.
// Since MRI accepts these values, we need to translate them.
case "MET": zone = "CET"; break;
case "ROC": zone = "Asia/Taipei"; break; // Republic of China
case "WET": zone = "Europe/Lisbon"; break; // Western European Time
// MRI behavior: With TZ equal to "GMT" or "UTC", Time.now
// is *NOT* considered as a proper GMT/UTC time:
// ENV['TZ']="GMT"; Time.now.gmt? #=> false
// ENV['TZ']="UTC"; Time.now.utc? #=> false
// Hence, we need to adjust for that.
zone = "Etc/" + upZone;
case "UTC": zone = "Etc/UTC"; break;
case "GMT": zone = "Etc/GMT"; break;
}
}

try {
return DateTimeZone.forID(zone);
} catch (IllegalArgumentException e) {
runtime.getWarnings().warning("Unrecognized time zone: "+zone);
return DateTimeZone.UTC;
}
try {
return DateTimeZone.forID(zone);
} catch (IllegalArgumentException e) {
runtime.getWarnings().warning("Unrecognized time zone: " + zone);
return DateTimeZone.UTC;
}
}

@@ -228,14 +220,15 @@ private static DateTimeZone parseZoneString(Ruby runtime, String zone) {
return parseTZString(runtime, zone);
}

public static DateTimeZone getTimeZoneFromUtcOffset(Ruby runtime, IRubyObject utcOffset) {
public static DateTimeZone getTimeZoneFromUtcOffset(ThreadContext context, IRubyObject utcOffset) {
final Ruby runtime = context.runtime;
String strOffset = utcOffset.toString();

DateTimeZone cachedZone = runtime.getTimezoneCache().get(strOffset);
if (cachedZone != null) return cachedZone;

DateTimeZone dtz;
if(utcOffset instanceof RubyString) {
if (utcOffset instanceof RubyString) {
Matcher offsetMatcher = TIME_OFFSET_PATTERN.matcher(strOffset);
if (!offsetMatcher.matches()) {
throw runtime.newArgumentError("\"+HH:MM\" or \"-HH:MM\" expected for utc_offset");
@@ -246,8 +239,8 @@ public static DateTimeZone getTimeZoneFromUtcOffset(Ruby runtime, IRubyObject ut
String seconds = offsetMatcher.group(4);
dtz = getTimeZoneFromHHMM(runtime, "", !sign.equals("-"), hours, minutes, seconds);
} else {
IRubyObject numericOffset = numExact(runtime, utcOffset);
int newOffset = (int)Math.round(numericOffset.convertToFloat().getDoubleValue() * 1000);
RubyNumeric numericOffset = numExact(context, utcOffset);
int newOffset = (int) Math.round(numericOffset.convertToFloat().getDoubleValue() * 1000);
dtz = getTimeZoneWithOffset(runtime, "", newOffset);
}

@@ -256,49 +249,43 @@ public static DateTimeZone getTimeZoneFromUtcOffset(Ruby runtime, IRubyObject ut
}

// mri: time.c num_exact
private static IRubyObject numExact(Ruby runtime, IRubyObject v) {
IRubyObject tmp;
private static RubyNumeric numExact(ThreadContext context, IRubyObject v) {
boolean typeError = false;

switch (v.getMetaClass().getClassIndex()) {
case INTEGER:
return v;
case NIL:
throw context.runtime.newTypeError("can't convert nil into an exact number");

case RATIONAL:
break;
case INTEGER: return (RubyInteger) v;

case STRING:
case NIL:
typeError = true;
break;
case RATIONAL: break;

case STRING: typeError = true; break;

default:
ThreadContext context = runtime.getCurrentContext();
if ((tmp = v.getMetaClass().finvokeChecked(context, v, "to_r")) != null) {
IRubyObject tmp;
if ((tmp = v.getMetaClass().finvokeChecked(context, v, sites(context).checked_to_r)) != null) {
/* test to_int method availability to reject non-Numeric
* objects such as String, Time, etc which have to_r method. */
if (!v.respondsTo("to_int")) {
typeError = true;
break;
if (!sites(context).respond_to_to_int.respondsTo(context, v, v)) {
typeError = true; break;
}
v = tmp;
break;
v = tmp; break;
}
if (!(tmp = TypeConverter.checkIntegerType(context, v)).isNil()) {
v = tmp;
break;
v = tmp; // return tmp;
}
else {
typeError = true;
}
typeError = true;
break;
}

switch (v.getMetaClass().getClassIndex()) {
case INTEGER:
return v;
case INTEGER: return (RubyInteger) v;

case RATIONAL:
if (((RubyRational) v).getDenominator() == RubyFixnum.one(runtime)) {
v = ((RubyRational) v).getNumerator();
if (((RubyRational) v).getDenominator().isOne()) {
return ((RubyRational) v).getNumerator();
}
break;

@@ -308,16 +295,10 @@ private static IRubyObject numExact(Ruby runtime, IRubyObject v) {
}

if (typeError) {
if (v.isNil()) throw runtime.newTypeError("can't convert nil into an exact number");
throw runtime.newTypeError("can't convert " + v.getMetaClass() + " into an exact number");
throw context.runtime.newTypeError("can't convert " + v.getMetaClass() + " into an exact number");
}

return v;
}

private static void exactTypeError(Ruby runtime, IRubyObject received) {
throw runtime.newTypeError(
String.format("Can't convert %s into an exact number", received.getMetaClass().getRealClass()));
return (RubyNumeric) v;
}

private static DateTimeZone getTimeZoneFromHHMM(Ruby runtime, String name, boolean positive, String hours, String minutes, String seconds) {
@@ -515,7 +496,7 @@ public RubyTime localtime(ThreadContext context) {

@JRubyMethod(name = "localtime")
public RubyTime localtime(ThreadContext context, IRubyObject arg) {
final DateTimeZone zone = getTimeZoneFromUtcOffset(context.runtime, arg);
final DateTimeZone zone = getTimeZoneFromUtcOffset(context, arg);
return adjustTimeZone(context.runtime, zone);
}

@@ -558,10 +539,10 @@ public RubyTime getlocal(ThreadContext context) {

@JRubyMethod(name = "getlocal")
public RubyTime getlocal(ThreadContext context, IRubyObject arg) {
if (arg.isNil()) {
if (arg == context.nil) {
return newTime(context.runtime, dt.withZone(getLocalTimeZone(context.runtime)), nsec);
}
DateTimeZone dtz = getTimeZoneFromUtcOffset(context.runtime, arg);
DateTimeZone dtz = getTimeZoneFromUtcOffset(context, arg);
return newTime(context.runtime, dt.withZone(dtz), nsec);
}

@@ -660,13 +641,10 @@ public IRubyObject op_plus(IRubyObject other) {
@JRubyMethod(name = "+", required = 1)
public IRubyObject op_plus(ThreadContext context, IRubyObject other) {
if (other instanceof RubyTime) {
throw context.runtime.newTypeError("time + time ?");
throw context.runtime.newTypeError("time + time?");
}

checkOpCoercion(context, other);
other = sites(context).to_r.call(context, other, other);

double adjustMillis = RubyNumeric.num2dbl(other) * 1000;
double adjustMillis = RubyNumeric.num2dbl(numExact(context, other)) * 1000;
return opPlusMillis(context.runtime, adjustMillis);
}

@@ -694,19 +672,6 @@ private RubyTime opPlusMillis(final Ruby runtime, double adjustMillis) {
return newTime;
}

private static void checkOpCoercion(ThreadContext context, IRubyObject other) {
//if (other instanceof RubyNumeric) return; // TODO MRI does num_exact here!
if (other instanceof RubyString) {
throw context.runtime.newTypeError("no implicit conversion to rational from string");
}
if (other == context.nil) {
throw context.runtime.newTypeError("no implicit conversion to rational from nil");
}
if (!sites(context).respond_to_to_r.respondsTo(context, other, other)){
throw context.runtime.newTypeError("can't convert " + other.getMetaClass().getBaseName() + " into Rational");
}
}

private RubyFloat opMinus(Ruby runtime, RubyTime other) {
long timeInMillis = getTimeInMillis() - other.getTimeInMillis();
double timeInSeconds = timeInMillis / 1000.0 + (getNSec() - other.getNSec()) / 1000000000.0;
@@ -722,10 +687,7 @@ public IRubyObject op_minus(IRubyObject other) {
public IRubyObject op_minus(ThreadContext context, IRubyObject other) {
if (other instanceof RubyTime) return opMinus(context.runtime, (RubyTime) other);

checkOpCoercion(context, other);
other = sites(context).to_r.call(context, other, other);

return opMinus(context.runtime, RubyNumeric.num2dbl(other));
return opMinus(context.runtime, RubyNumeric.num2dbl(numExact(context, other)));
}

@Deprecated
@@ -1136,7 +1098,7 @@ public static IRubyObject at(ThreadContext context, IRubyObject recv, IRubyObjec
long nanosecs;
long millisecs;

arg = numExact(runtime, arg);
arg = numExact(context, arg);

// In the case of two arguments, MRI will discard the portion of
// the first argument after a decimal point (i.e., "floor").
@@ -1201,8 +1163,8 @@ public static IRubyObject at(ThreadContext context, IRubyObject recv, IRubyObjec
long millisecs;
long nanosecs = 0;

arg1 = numExact(runtime, arg1);
arg2 = numExact(runtime, arg2);
arg1 = numExact(context, arg1);
arg2 = numExact(context, arg2);

if (arg1 instanceof RubyFloat || arg1 instanceof RubyRational) {
double dbl = RubyNumeric.num2dbl(arg1);
@@ -1442,7 +1404,7 @@ private static RubyTime createTime(ThreadContext context, RubyClass klass, IRuby
dtz = DateTimeZone.UTC;
} else if (args.length == 10 && args[9] instanceof RubyString) {
if (utcOffset) {
dtz = getTimeZoneFromUtcOffset(runtime, args[9]);
dtz = getTimeZoneFromUtcOffset(context, args[9]);
setTzRelative = true;
} else {
dtz = getTimeZoneFromString(runtime, args[9].toString());
2 changes: 1 addition & 1 deletion core/src/main/java/org/jruby/runtime/JavaSites.java
Original file line number Diff line number Diff line change
@@ -292,7 +292,7 @@ public IRubyObject call(ThreadContext context, IRubyObject recv, IRubyObject oth
public final CachingCallSite to_int = new FunctionalCachingCallSite("to_int");
public final CachingCallSite to_i = new FunctionalCachingCallSite("to_i");
public final CachingCallSite to_r = new FunctionalCachingCallSite("to_r");
public final RespondToCallSite respond_to_to_r = new RespondToCallSite("to_r");
public final CheckedSites checked_to_r = new CheckedSites("to_r");
}

public static class EnumerableSites {
6 changes: 3 additions & 3 deletions core/src/main/java/org/jruby/util/Numeric.java
Original file line number Diff line number Diff line change
@@ -623,7 +623,7 @@ static boolean fitSqrtLong(long n) {
return n < SQRT_LONG_MAX && n >= -SQRT_LONG_MAX;
}

public static IRubyObject int_pow(ThreadContext context, long x, long y) {
public static RubyNumeric int_pow(ThreadContext context, long x, long y) {
boolean neg = x < 0;
long z = 1;
if (neg) x = -x;
@@ -641,7 +641,7 @@ public static IRubyObject int_pow(ThreadContext context, long x, long y) {
if (!fitSqrtLong(x)) {
IRubyObject v = RubyBignum.newBignum(runtime, x).op_pow(context, y);
if (z != 1) v = RubyBignum.newBignum(runtime, neg ? -z : z).op_mul(context, v);
return v;
return (RubyNumeric) v;
}
x *= x;
y >>= 1;
@@ -650,7 +650,7 @@ public static IRubyObject int_pow(ThreadContext context, long x, long y) {
if (multiplyOverflows(x, z)) {
IRubyObject v = RubyBignum.newBignum(runtime, x).op_pow(context, y);
if (z != 1) v = RubyBignum.newBignum(runtime, neg ? -z : z).op_mul(context, v);
return v;
return (RubyNumeric) v;
}
z = x * z;
} while(--y != 0);
14 changes: 8 additions & 6 deletions test/jruby/test_time.rb
Original file line number Diff line number Diff line change
@@ -60,22 +60,23 @@ def test_far_future
end

class TestTimeNilOps < Test::Unit::TestCase

def test_minus
begin
Time.now - ()
rescue TypeError=>x
assert x
assert_equal "no implicit conversion to rational from nil", x.message
rescue TypeError => x
assert_equal "can't convert nil into an exact number", x.message
end
end

def test_plus
begin
Time.now + ()
rescue TypeError=>x
assert x
assert_equal "no implicit conversion to rational from nil", x.message
rescue TypeError => x
assert_equal "can't convert nil into an exact number", x.message
end
end

def test_times
t = Time.now
begin
@@ -86,6 +87,7 @@ def test_times
assert_equal "undefined method `*' for #{t}:Time", x.message
end
end

def test_div
t = Time.now
begin