Permalink
Browse files

Add support for Ruby 1.9.2's "coverage" extension.

  • Loading branch information...
1 parent bf612db commit aaa325082d1bbb86c89f8c6fe1f16b7d38f7bf21 @headius headius committed Aug 17, 2011
View
@@ -125,7 +125,9 @@
import com.kenai.constantine.platform.Errno;
import java.io.ByteArrayOutputStream;
import java.io.File;
+import java.io.FileInputStream;
import java.nio.channels.ClosedChannelException;
+import java.nio.channels.FileChannel;
import java.util.EnumSet;
import java.util.concurrent.atomic.AtomicLong;
import org.jcodings.specific.USASCIIEncoding;
@@ -134,6 +136,7 @@
import org.jruby.ast.executable.RuntimeCache;
import org.jruby.evaluator.ASTInterpreter;
import org.jruby.exceptions.Unrescuable;
+import org.jruby.ext.coverage.CoverageData;
import org.jruby.internal.runtime.methods.DynamicMethod;
import org.jruby.interpreter.Interpreter;
import org.jruby.javasupport.util.RuntimeHelpers;
@@ -144,6 +147,7 @@
import org.jruby.runtime.MethodIndex;
import org.jruby.runtime.load.BasicLibraryService;
import org.jruby.threading.DaemonThreadFactory;
+import org.jruby.util.JRubyFile;
import org.jruby.util.io.SelectorPool;
/**
@@ -1456,6 +1460,7 @@ private void initBuiltins() {
addLazyBuiltin("mathn/rational.jar", "mathn/rational", "org.jruby.ext.mathn.Rational");
addLazyBuiltin("fiber.rb", "fiber", "org.jruby.libraries.FiberExtLibrary");
addLazyBuiltin("psych.jar", "psych", "org.jruby.ext.psych.PsychLibrary");
+ addLazyBuiltin("coverage.jar", "coverage", "org.jruby.ext.coverage.CoverageLibrary");
}
if(RubyInstanceConfig.NATIVE_NET_PROTOCOL) {
@@ -3886,6 +3891,10 @@ public void setFloatReopened(boolean floatReopened) {
public boolean isBooting() {
return booting;
}
+
+ public CoverageData getCoverageData() {
+ return coverageData;
+ }
private volatile int constantGeneration = 1;
private final ThreadService threadService;
@@ -4108,4 +4117,6 @@ public boolean isBooting() {
private volatile boolean booting = true;
private RubyHash envObject;
+
+ private final CoverageData coverageData = new CoverageData();
}
@@ -0,0 +1,69 @@
+package org.jruby.ext.coverage;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.jruby.Ruby;
+import org.jruby.runtime.EventHook;
+import org.jruby.runtime.RubyEvent;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class CoverageData {
+ private volatile Map<String, Integer[]> coverage;
+
+ public boolean isCoverageEnabled() {
+ return coverage != null;
+ }
+
+ public synchronized void setCoverageEnabled(Ruby runtime, boolean enabled) {
+ if (enabled) {
+ coverage = new HashMap<String, Integer[]>();
+ runtime.addEventHook(COVERAGE_HOOK);
+ } else {
+ coverage = null;
+ }
+ }
+
+ public synchronized Map<String, Integer[]> resetCoverage(Ruby runtime) {
+ Map<String, Integer[]> coverage = this.coverage;
+ runtime.removeEventHook(COVERAGE_HOOK);
+ this.coverage = null;
+
+ return coverage;
+ }
+
+ private final EventHook COVERAGE_HOOK = new EventHook() {
+ @Override
+ public synchronized void eventHandler(ThreadContext context, String eventName, String file, int line, String name, IRubyObject type) {
+ if (coverage == null) {
+ return;
+ }
+
+ // make sure we have a lines array of acceptable length for the given file
+ Integer[] lines = coverage.get(file);
+ if (lines == null) {
+ lines = new Integer[line];
+ coverage.put(file, lines);
+ } else if (lines.length <= line) {
+ Integer[] newLines = new Integer[line];
+ System.arraycopy(lines, 0, newLines, 0, lines.length);
+ lines = newLines;
+ coverage.put(file, lines);
+ }
+
+ // increment the line's count or set it to 1
+ Integer count = lines[line - 1];
+ if (count == null) {
+ lines[line - 1] = 1;
+ } else {
+ lines[line - 1] = count + 1;
+ }
+ }
+
+ @Override
+ public boolean isInterestedInEvent(RubyEvent event) {
+ return event == RubyEvent.LINE;
+ }
+ };
+
+}
@@ -0,0 +1,23 @@
+package org.jruby.ext.coverage;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyHash;
+import org.jruby.RubyModule;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.RubyEvent;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.runtime.load.Library;
+
+public class CoverageLibrary implements Library {
+ public void load(Ruby runtime, boolean wrap) {
+ RubyModule coverage = runtime.defineModule("Coverage");
+
+ coverage.defineAnnotatedMethods(CoverageModule.class);
+ }
+}
@@ -0,0 +1,51 @@
+package org.jruby.ext.coverage;
+
+import java.util.Map;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyHash;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+/**
+ * Implementation of Ruby 1.9.2's "Coverage" module
+ */
+public class CoverageModule {
+ @JRubyMethod(module = true)
+ public static IRubyObject start(ThreadContext context, IRubyObject self) {
+ Ruby runtime = context.runtime;
+
+ if (!runtime.getCoverageData().isCoverageEnabled()) {
+ runtime.getCoverageData().setCoverageEnabled(runtime, true);
+ }
+
+ return context.nil;
+ }
+
+ @JRubyMethod(module = true)
+ public static IRubyObject result(ThreadContext context, IRubyObject self) {
+ Ruby runtime = context.runtime;
+
+ if (!runtime.getCoverageData().isCoverageEnabled()) {
+ throw runtime.newRuntimeError("coverage measurement is not enabled");
+ }
+
+ Map<String, Integer[]> coverage = runtime.getCoverageData().resetCoverage(runtime);
+
+ // populate a Ruby Hash with coverage data
+ RubyHash covHash = RubyHash.newHash(runtime);
+ for (Map.Entry<String, Integer[]> entry : coverage.entrySet()) {
+ RubyArray ary = RubyArray.newArray(runtime, entry.getValue().length);
+ for (int i = 0; i < entry.getValue().length; i++) {
+ Integer integer = entry.getValue()[i];
+ ary.store(i, integer == null ? runtime.getNil() : runtime.newFixnum(integer));
+ covHash.fastASetCheckString(runtime, RubyString.newString(runtime, entry.getKey()), ary);
+ }
+ }
+
+ return covHash;
+ }
+
+}
@@ -14,7 +14,8 @@
RETURN ("return", 1),
C_CALL ("c-call", 1),
C_RETURN ("c-return", 1),
- RAISE ("raise", 1);
+ RAISE ("raise", 1),
+ COVERAGE ("coverage", 1);
private final String event_name;
private final int line_number_offset;
@@ -410,6 +410,9 @@ public static void reflectedLoad(Ruby runtime, String libraryName, String classN
} else if (libObject instanceof BasicLibraryService) {
BasicLibraryService service = (BasicLibraryService)libObject;
service.basicLoad(runtime);
+ } else {
+ // invalid type of library, raise error
+ throw runtime.newLoadError("library `" + libraryName + "' is not of type Library or BasicLibraryService");
}
} catch (RaiseException re) {
throw re;

0 comments on commit aaa3250

Please sign in to comment.