2626
2727package org .jruby .ext .coverage ;
2828
29- import java .util .Arrays ;
30- import java .util .EnumSet ;
3129import java .util .HashMap ;
3230import java .util .Map ;
33- import org .jruby .Ruby ;
34- import org .jruby .runtime .EventHook ;
35- import org .jruby .runtime .RubyEvent ;
36- import org .jruby .runtime .ThreadContext ;
37- import org .jruby .runtime .builtin .IRubyObject ;
31+ import org .jruby .util .collections .IntList ;
3832
3933public class CoverageData {
4034 public static final String STARTED = "" ; // no load/require ruby file can be "" so we
41- private static final int [] SVALUE = new int [0 ]; // use it as a holder to know if start occurs
42- private volatile Map <String , int []> coverage ;
35+ private static final IntList SVALUE = new IntList (); // use it as a holder to know if start occurs
36+ private volatile Map <String , IntList > coverage ;
37+ private volatile int mode ;
38+
39+ public static final int NONE = 0 ;
40+ public static final int LINES = 1 << 0 ;
41+ public static final int BRANCHES = 1 << 1 ;
42+ public static final int METHODS = 1 << 2 ;
43+ public static final int ONESHOT_LINES = 1 << 3 ;
44+ public static final int ALL = LINES | BRANCHES | METHODS ;
4345
4446 public boolean isCoverageEnabled () {
45- return coverage != null && coverage .get (STARTED ) != null ;
47+ return mode != 0 ;
48+ }
49+
50+ public int getMode () {
51+ return mode ;
4652 }
4753
48- public Map <String , int []> getCoverage () {
54+ public boolean isOneshot () {
55+ return (mode & ONESHOT_LINES ) != 0 ;
56+ }
57+
58+ public Map <String , IntList > getCoverage () {
4959 return coverage ;
5060 }
5161
52- public synchronized void setCoverageEnabled (Ruby runtime , boolean enabled ) {
53- Map <String , int []> coverage = this .coverage ;
62+ /**
63+ * Update coverage data for the given file and line number.
64+ *
65+ * @param filename
66+ * @param line
67+ */
68+ public synchronized void coverLine (String filename , int line ) {
69+ IntList lines = coverage .get (filename );
70+
71+ if (lines == null ) return ;
72+
73+ if (isOneshot ()) {
74+ lines .add (line );
75+ } else {
76+ if (lines .size () <= line ) return ;
77+ lines .set (line , lines .get (line ) + 1 );
78+ }
79+ }
80+
81+ public synchronized void setCoverageEnabled (int mode ) {
82+ Map <String , IntList > coverage = this .coverage ;
5483
55- if (coverage == null ) coverage = new HashMap <String , int [] >();
84+ if (coverage == null ) coverage = new HashMap <>();
5685
57- if (enabled ) {
86+ if (mode != CoverageData . NONE ) {
5887 coverage .put (STARTED , SVALUE );
59- runtime .addEventHook (COVERAGE_HOOK );
6088 } else {
6189 coverage .remove (STARTED );
6290 }
6391
6492 this .coverage = coverage ;
93+ this .mode = mode ;
6594 }
6695
67- public synchronized Map <String , int []> resetCoverage (Ruby runtime ) {
68- Map <String , int []> coverage = this .coverage ;
69- runtime .removeEventHook (COVERAGE_HOOK );
96+ public synchronized Map <String , IntList > resetCoverage () {
97+ Map <String , IntList > coverage = this .coverage ;
7098 coverage .remove (STARTED );
7199
72-
73- for (Map .Entry <String , int []> entry : coverage .entrySet ()) {
100+ for (Map .Entry <String , IntList > entry : coverage .entrySet ()) {
74101 String key = entry .getKey ();
75102
76103 // on reset we do not reset files where no execution ever happened but we do reset
@@ -79,22 +106,21 @@ public synchronized Map<String, int[]> resetCoverage(Ruby runtime) {
79106 }
80107
81108 this .coverage = null ;
109+ this .mode = CoverageData .NONE ;
82110
83111 return coverage ;
84112 }
85113
86- private static boolean hasCodeBeenPartiallyCovered (int [] lines ) {
87- for (int i = 0 ; i < lines .length ; i ++) {
88- if (lines [ i ] > 0 ) return true ;
114+ private static boolean hasCodeBeenPartiallyCovered (IntList lines ) {
115+ for (int i = 0 ; i < lines .size () ; i ++) {
116+ if (lines . get ( i ) > 0 ) return true ;
89117 }
90118
91119 return false ;
92120 }
93121
94- public synchronized Map <String , int []> prepareCoverage (String filename , int [] lines ) {
95- assert lines != null ;
96-
97- Map <String , int []> coverage = this .coverage ;
122+ public synchronized Map <String , IntList > prepareCoverage (String filename , int [] startingLines ) {
123+ Map <String , IntList > coverage = this .coverage ;
98124
99125 if (filename == null ) {
100126 // null filename from certain evals, Ruby.executeScript, etc (jruby/jruby#5111)
@@ -103,45 +129,14 @@ public synchronized Map<String, int[]> prepareCoverage(String filename, int[] li
103129 }
104130
105131 if (coverage != null ) {
106- coverage .put (filename , lines );
132+ if (isOneshot ()) {
133+ coverage .put (filename , new IntList ());
134+ } else {
135+ coverage .put (filename , new IntList (startingLines ));
136+ }
107137 }
108138
109139 return coverage ;
110140 }
111-
112- private static final EnumSet <RubyEvent > COVERAGE_EVENTS = EnumSet .of (RubyEvent .COVERAGE );
113-
114- private final EventHook COVERAGE_HOOK = new EventHook () {
115- @ Override
116- public void eventHandler (ThreadContext context , String eventName , String file , int line , String name , IRubyObject type ) {
117- synchronized (CoverageData .this ) {
118- Map <String , int []> coverage = CoverageData .this .coverage ;
119-
120- // Should not be needed but I predict serialization of IR might hit this.
121- if (coverage == null || line <= 0 ) return ;
122-
123- int [] lines = coverage .get (file );
124-
125- // no coverage lines for this record. bail out (should never happen)
126- if (lines == null ) return ;
127-
128- // coverage is dead for this record. result() has been called once and we marked it as such as an empty list.
129- if (lines .length == 0 ) return ;
130-
131- // increment usage count by one.
132- lines [line - 1 ] += 1 ;
133- }
134- }
135-
136- @ Override
137- public boolean isInterestedInEvent (RubyEvent event ) {
138- return event == RubyEvent .COVERAGE ;
139- }
140-
141- @ Override
142- public EnumSet <RubyEvent > eventSet () {
143- return COVERAGE_EVENTS ;
144- }
145- };
146141
147142}
0 commit comments