diff --git a/CHANGELOG.md b/CHANGELOG.md index a6d4ba9..412475a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.7.0 + - Added new builtin rules that remove lines of code (void function calls and contents of arrays) + - Coverage information is provided in the html report: line numbers of instrumented lines are displayed in red and executed lines are displayed in green + ## 1.6.0 - Added the option to exclude lines not covered by tests. You can pass a file with coverage information in the lcov format via the command line flag "--coverage". diff --git a/doc/output/example/source.dart.html b/doc/output/example/source.dart.html index ea865b9..1527b8d 100644 --- a/doc/output/example/source.dart.html +++ b/doc/output/example/source.dart.html @@ -17,6 +17,13 @@ background-color: #DAE7FE; } +.covered { + background-color: #9EFF83; +} +.instrumented { + background-color: #FF8983; +} + .hit:hover { color: white; background-color: #6688D4; @@ -308,6 +315,12 @@ .hit { background-color: #26334F; } + .covered { + background-color: #0A3302; + } + .instrumented { + background-color: #391D1D; + } .addedLine { background-color: rgb(37, 83, 37); } @@ -320,7 +333,6 @@ a:link, a:visited { color: #58a6ff; } - .match .tooltip { background-color: black; color: #fff; @@ -348,7 +360,7 @@ Date: - 2024-03-24 15:27:37.230520 + 2024-03-24 17:47:31.491803 Mutations: 22 42 @@ -359,9 +371,9 @@ Builtin rules: true Blocked: - 0 + 5 42 - 0.0 % + 11.9 % @@ -379,8 +391,8 @@ 2 // License: BSD-3-Clause 3 // See LICENSE for the full text of the license 4 - 5 int conditions(int a, int b, int c) { - + 5 int conditions(int a, int b, int c) { +
Undetected mutations: @@ -389,13 +401,13 @@
1 :+ if (a == b || (a < c || b > c || b == c)) { &&Id: builtin.and

2 :+ if (a == b && (a < c && b > c || b == c)) { ||Id: builtin.or

3 :+ if (a != b && (a < c || b > c || b == c)) { ==Id: builtin.op.eq

4 :+ if (!(a == b && (a < c || b > c || b == c))) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

5 :+ if (!(a == b )&& (a < c || b > c || b == c)) { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start

6 :+ if (a == b &&!(a < c || b > c || b == c)) { RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not
-
+
Detected mutations:
1 :+ return a - c; RegExp: pattern=\+([^=]) flags=mId: builtin.arith.add
-
+
Undetected mutations: @@ -404,19 +416,19 @@
1 :+ } else if (b == 0 && c > 0) { <=Id: builtin.op.leq

2 :+ } else if (!(b <= 0 && c > 0)) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

3 :+ } else if (!(b <= 0 )&& c > 0) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

4 :+ } else if (b <= 0 &&!( c > 0)) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

5 :+ } else if (!(b <= 0 )&& c > 0) { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start

6 :+ } else if (b <= 0 &&!( c > 0)) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
-
+
Detected mutations:
1 :+ return a + b; RegExp: pattern=-([^=]) flags=mId: builtin.arith.sub
10 } - 11 for (var i = 0; i < 10; ++i) {} + 11 for (var i = 0; i < 10; ++i) {} 12 var i = 0; - 13 while (i < 10) { - 14 ++i; + 13 while (i < 10) { + 14 ++i; 15 } - +
Undetected mutations: @@ -427,8 +439,8 @@ 17 } 18 - 19 double poly(double x, double a, double b, double c) { - + 19 double poly(double x, double a, double b, double c) { +
Detected mutations:
@@ -436,8 +448,8 @@ 21 } 22 - 23 double inner2(double x, double y, double z) { - + 23 double inner2(double x, double y, double z) { +
Undetected mutations:
@@ -448,8 +460,8 @@ 25 } 26 - 27 double inner(double x, double y) { - + 27 double inner(double x, double y) { +
Undetected mutations:
@@ -460,21 +472,21 @@ 29 } 30 - 31 double outer(double x, double y) { - 32 return optional(x: x, y: y); + 31 double outer(double x, double y) { + 32 return optional(x: x, y: y); 33 } 34 - +
Undetected mutations:
1 :+ double optional({double x = -1.0, double y = 2.0}) => inner(x, y); RegExp: pattern=([\s=\(])([1-9\.]+[0-9]+|0\.0*[1-9]) flags=mId: builtin.number.negative

2 :+ double optional({double x = 1.0, double y = -2.0}) => inner(x, y); RegExp: pattern=([\s=\(])([1-9\.]+[0-9]+|0\.0*[1-9]) flags=mId: builtin.number.negative

3 :+ double optional({double x = 1.0, double y = 2.0}) => inner( y,x); RegExp: pattern=([\s][a-zA-Z]+?[^(;\s{}]*?)\s*\(([^,:;{}(]+?),([^,:;{}(]+?)\)\s*; flags=mId: builtin.function.arg2
36 - 37 double polyNotCovered(double x, double a, double b, double c) { - + 37 double polyNotCovered(double x, double a, double b, double c) { +
-Undetected mutations: +Mutations not covered by tests:
1 :+ return a * x * x - b * x + c; RegExp: pattern=\+([^=]) flags=mId: builtin.arith.add

2 :+ return a * x * x + b * x - c; RegExp: pattern=\+([^=]) flags=mId: builtin.arith.add

3 :+ return a / x * x + b * x + c; RegExp: pattern=\*([^=]) flags=mId: builtin.arith.mul

4 :+ return a * x / x + b * x + c; RegExp: pattern=\*([^=]) flags=mId: builtin.arith.mul

5 :+ return a * x * x + b / x + c; RegExp: pattern=\*([^=]) flags=mId: builtin.arith.mul
@@ -506,7 +518,7 @@ } - +

diff --git a/doc/output/example/source2.dart.html b/doc/output/example/source2.dart.html index 8c91143..48c86ae 100644 --- a/doc/output/example/source2.dart.html +++ b/doc/output/example/source2.dart.html @@ -17,6 +17,13 @@ background-color: #DAE7FE; } +.covered { + background-color: #9EFF83; +} +.instrumented { + background-color: #FF8983; +} + .hit:hover { color: white; background-color: #6688D4; @@ -308,6 +315,12 @@ .hit { background-color: #26334F; } + .covered { + background-color: #0A3302; + } + .instrumented { + background-color: #391D1D; + } .addedLine { background-color: rgb(37, 83, 37); } @@ -320,7 +333,6 @@ a:link, a:visited { color: #58a6ff; } - .match .tooltip { background-color: black; color: #fff; @@ -348,7 +360,7 @@ Date: - 2024-03-24 15:27:37.230520 + 2024-03-24 17:47:31.491803 Mutations: 2 56 @@ -359,9 +371,9 @@ Builtin rules: true Blocked: - 0 + 32 56 - 0.0 % + 57.1 % @@ -398,8 +410,8 @@
12 bool on = false; 13 - 14 double calc(double x) { - + 14 double calc(double x) { +
Undetected mutations: @@ -411,29 +423,29 @@ 16 } 17 18 // just a weird example ... - 19 String format(double y) { - + 19 String format(double y) { +
Undetected mutations:
-
1 :+ if (!(y <= 0.0 )&& text != '') { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start

2 :+ if (y == 0.0 && text != '') { <=Id: builtin.op.leq

3 :+ if (y <= 0.0 && text == '') { !=Id: builtin.op.neq

4 :+ if (y <= 0.0 || text != '') { &&Id: builtin.and

5 :+ if (y < 0.0 && text != '') { <=Id: builtin.op.leq

6 :+ if (!(y <= 0.0 && text != '')) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

7 :+ if (!(y <= 0.0 )&& text != '') { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

8 :+ if (y <= 0.0 &&!( text != '')) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

9 :+ if (y <= 0.0 &&!( text != '')) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
+1 :+ if (y <= 0.0 || text != '') { &&Id: builtin.and
2 :+ if (y <= 0.0 && text == '') { !=Id: builtin.op.neq
3 :+ if (y == 0.0 && text != '') { <=Id: builtin.op.leq
4 :+ if (y < 0.0 && text != '') { <=Id: builtin.op.leq
5 :+ if (!(y <= 0.0 && text != '')) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if
6 :+ if (!(y <= 0.0 )&& text != '') { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
7 :+ if (y <= 0.0 &&!( text != '')) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
8 :+ if (!(y <= 0.0 )&& text != '') { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start
9 :+ if (y <= 0.0 &&!( text != '')) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end -
21 return '$text $y'; - + 21 return '$text $y'; +
Undetected mutations: -
1 :+ } else if (y != 0.0 && text != '') { ==Id: builtin.op.eq

2 :+ } else if (y == 0.0 && text == '') { !=Id: builtin.op.neq

3 :+ } else if (!(y == 0.0 && text != '')) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

4 :+ } else if (!(y == 0.0 )&& text != '') { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

5 :+ } else if (y == 0.0 &&!( text != '')) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

6 :+ } else if (y == 0.0 || text != '') { &&Id: builtin.and

7 :+ } else if (!(y == 0.0 )&& text != '') { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start

8 :+ } else if (y == 0.0 &&!( text != '')) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
+1 :+ } else if (y == 0.0 || text != '') { &&Id: builtin.and
2 :+ } else if (y != 0.0 && text != '') { ==Id: builtin.op.eq
3 :+ } else if (y == 0.0 && text == '') { !=Id: builtin.op.neq
4 :+ } else if (!(y == 0.0 && text != '')) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if
5 :+ } else if (!(y == 0.0 )&& text != '') { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
6 :+ } else if (y == 0.0 &&!( text != '')) { RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
7 :+ } else if (!(y == 0.0 )&& text != '') { RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start
8 :+ } else if (y == 0.0 &&!( text != '')) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end -
23 return '$text $y'; + 23 return '$text $y'; 24 } - 25 return 'default $y'; + 25 return 'default $y'; 26 } 27 - 28 void changeState(dynamic event) { - + 28 void changeState(dynamic event) { +
-Undetected mutations: +Mutations not covered by tests:
1 :+ if (event.a || &&Id: builtin.and

2 :+ if (!(event.a && event.b && @@ -441,29 +453,29 @@ event.e)) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

3 :+ if (event.a &&!( event.b )&& RegExp: pattern=&([^&()]+?)& flags=msId: builtin.logical.and_chain

4 :+ if (!(event.a )&& RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start
-
+
-Undetected mutations: +Mutations not covered by tests:
1 :+ event.b || &&Id: builtin.and

2 :+ event.b &&!(event.c || event.d || (event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not
-
+
-Undetected mutations: +Mutations not covered by tests: -
1 :+ (event.c || event.d || (event.f && event.g)) || &&Id: builtin.and

2 :+ (event.c ||!( event.d )|| (event.f && event.g)) && RegExp: pattern=\|([^|()]+?)\| flags=msId: builtin.logical.or_chain

3 :+ (event.c || event.d ||!(event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not

4 :+ (event.c || event.d || (event.f || event.g)) && &&Id: builtin.and

5 :+ (event.c && event.d || (event.f && event.g)) && ||Id: builtin.or

6 :+ (event.c || event.d || (!(event.f )&& event.g)) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

7 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

8 :+ (event.c || event.d || (event.f && event.g)) &&!( - event.e)) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end

9 :+ (event.c || event.d && (event.f && event.g)) && ||Id: builtin.or

10 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
+1 :+ (event.c || event.d || (event.f || event.g)) && &&Id: builtin.and
2 :+ (event.c || event.d || (event.f && event.g)) || &&Id: builtin.and
3 :+ (event.c && event.d || (event.f && event.g)) && ||Id: builtin.or
4 :+ (event.c || event.d && (event.f && event.g)) && ||Id: builtin.or
5 :+ (event.c ||!( event.d )|| (event.f && event.g)) && RegExp: pattern=\|([^|()]+?)\| flags=msId: builtin.logical.or_chain
6 :+ (event.c || event.d || (!(event.f )&& event.g)) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
7 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
8 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
9 :+ (event.c || event.d || (event.f && event.g)) &&!( + event.e)) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
10 :+ (event.c || event.d ||!(event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not -
32 event.e) { - 33 on = true; + 32 event.e) { + 33 on = true; 34 } 35 } 36 37 // excluded: - 38 void changeState2(dynamic event) { - + 38 void changeState2(dynamic event) { +
-Undetected mutations: +Mutations not covered by tests:
1 :+ if (event.a || &&Id: builtin.and

2 :+ if (!(event.a && event.b && @@ -471,57 +483,53 @@ event.e)) { RegExp: pattern=[\s]if[\s]*\((.*?)\)[\s]*{ flags=msId: builtin.if

3 :+ if (event.a &&!( event.b )&& RegExp: pattern=&([^&()]+?)& flags=msId: builtin.logical.and_chain

4 :+ if (!(event.a )&& RegExp: pattern=if\s*\(([^|&\)]*?)([|&][|&]) flags=mId: builtin.if.start
-
+
-Undetected mutations: +Mutations not covered by tests: -
1 :+ event.b &&!(event.c || event.d || (event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not

2 :+ event.b || &&Id: builtin.and
+1 :+ event.b || &&Id: builtin.and
2 :+ event.b &&!(event.c || event.d || (event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not -
+
-Undetected mutations: +Mutations not covered by tests: -
1 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

2 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end

3 :+ (event.c || event.d || (event.f && event.g)) &&!( - event.e)) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end

4 :+ (event.c || event.d || (!(event.f )&& event.g)) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2

5 :+ (event.c || event.d || (event.f && event.g)) || &&Id: builtin.and

6 :+ (event.c || event.d && (event.f && event.g)) && ||Id: builtin.or

7 :+ (event.c || event.d ||!(event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not

8 :+ (event.c && event.d || (event.f && event.g)) && ||Id: builtin.or

9 :+ (event.c || event.d || (event.f || event.g)) && &&Id: builtin.and

10 :+ (event.c ||!( event.d )|| (event.f && event.g)) && RegExp: pattern=\|([^|()]+?)\| flags=msId: builtin.logical.or_chain
+1 :+ (event.c || event.d || (event.f || event.g)) && &&Id: builtin.and
2 :+ (event.c || event.d || (event.f && event.g)) || &&Id: builtin.and
3 :+ (event.c && event.d || (event.f && event.g)) && ||Id: builtin.or
4 :+ (event.c || event.d && (event.f && event.g)) && ||Id: builtin.or
5 :+ (event.c ||!( event.d )|| (event.f && event.g)) && RegExp: pattern=\|([^|()]+?)\| flags=msId: builtin.logical.or_chain
6 :+ (event.c || event.d || (!(event.f )&& event.g)) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
7 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=\(([^$(]*?)&&([^$()]*?)\) flags=mId: builtin.logical.and_chain2
8 :+ (event.c || event.d || (event.f &&!( event.g))) && RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
9 :+ (event.c || event.d || (event.f && event.g)) &&!( + event.e)) { RegExp: pattern=([|&][|&])([^|&]*?)\) flags=mId: builtin.if.end
10 :+ (event.c || event.d ||!(event.f && event.g)) && RegExp: pattern=([|&][|&])[\s]*?\( flags=msId: builtin.logical.chain_not -
42 event.e) { - 43 on = true; + 42 event.e) { + 43 on = true; 44 } 45 } 46 47 List<double> values = []; - 48 void fillList(double x) { - + 48 void fillList(double x) { +
Detected mutations:
1 :+ values = []; RegExp: pattern=([=:>]\s*)\[([^\],]+),([^\]]+)\] flags=mId: builtin.list.clear
-
50 calc(x), - 51 calc(number1) - 52 ]; - 53 } - 54 - + 50 } + 51 +
Undetected mutations:
1 :+ double voidCalls(double x) { RegExp: pattern=([{]\s*)([^\(;=\s}]+\([^;]+\)\s*;) flags=mId: builtin.function.removeVoidCall1
-
+
Undetected mutations:
1 :+ fillList(x); RegExp: pattern=([};]\s*)([^\(;=\s}]+\([^;]+\)\s*;) flags=mId: builtin.function.removeVoidCall2
-
57 format(x); - 58 return x; - 59 } - 60 - 61 } - 62 + 54 format(x); + 55 return x; + 56 } + 57 } + 58 - +

diff --git a/doc/output/example/source3.dart.html b/doc/output/example/source3.dart.html index 5d7fb3b..6cb7146 100644 --- a/doc/output/example/source3.dart.html +++ b/doc/output/example/source3.dart.html @@ -17,6 +17,13 @@ background-color: #DAE7FE; } +.covered { + background-color: #9EFF83; +} +.instrumented { + background-color: #FF8983; +} + .hit:hover { color: white; background-color: #6688D4; @@ -308,6 +315,12 @@ .hit { background-color: #26334F; } + .covered { + background-color: #0A3302; + } + .instrumented { + background-color: #391D1D; + } .addedLine { background-color: rgb(37, 83, 37); } @@ -320,7 +333,6 @@ a:link, a:visited { color: #58a6ff; } - .match .tooltip { background-color: black; color: #fff; @@ -348,7 +360,7 @@ Date: - 2024-03-24 15:27:37.231554 + 2024-03-24 17:47:31.491803 Mutations: 0 0 @@ -407,7 +419,7 @@ } - +

diff --git a/doc/output/mutation-test-report.html b/doc/output/mutation-test-report.html index 17d2a79..bfba44d 100644 --- a/doc/output/mutation-test-report.html +++ b/doc/output/mutation-test-report.html @@ -17,6 +17,13 @@ background-color: #DAE7FE; } +.covered { + background-color: #9EFF83; +} +.instrumented { + background-color: #FF8983; +} + .hit:hover { color: white; background-color: #6688D4; @@ -308,6 +315,12 @@ .hit { background-color: #26334F; } + .covered { + background-color: #0A3302; + } + .instrumented { + background-color: #391D1D; + } .addedLine { background-color: rgb(37, 83, 37); } @@ -320,7 +333,6 @@ a:link, a:visited { color: #58a6ff; } - .match .tooltip { background-color: black; color: #fff; @@ -348,7 +360,7 @@ Date: - 2024-03-24 15:27:37.229545 + 2024-03-24 17:47:31.491803 Mutations: 24 98 @@ -359,9 +371,9 @@ Builtin rules: true Blocked: - 0 + 37 98 - 0.0 % + 37.8 % Quality rating: @@ -395,7 +407,7 @@ 52.4 % 22 / 42 - 0 / 42 + 5 / 42 example/source2.dart @@ -406,7 +418,7 @@ 3.6 % 2 / 56 - 0 / 56 + 32 / 56 example/source3.dart @@ -426,7 +438,7 @@ - +

diff --git a/example/lcov.info b/example/lcov.info new file mode 100644 index 0000000..3eca6cc --- /dev/null +++ b/example/lcov.info @@ -0,0 +1,53 @@ +SF:example\source2.dart +DA:14,1 +DA:15,4 +DA:19,1 +DA:20,3 +DA:21,2 +DA:22,1 +DA:23,0 +DA:25,1 +DA:28,0 +DA:29,0 +DA:30,0 +DA:31,0 +DA:32,0 +DA:33,0 +DA:38,0 +DA:39,0 +DA:40,0 +DA:41,0 +DA:42,0 +DA:43,0 +DA:48,1 +DA:49,5 +DA:52,1 +DA:53,1 +DA:54,1 +LF:25 +LH:12 +end_of_record +SF:example\source.dart +DA:5,1 +DA:6,2 +DA:7,1 +DA:8,2 +DA:9,1 +DA:11,2 +DA:13,1 +DA:14,1 +DA:16,2 +DA:19,1 +DA:20,5 +DA:23,1 +DA:24,1 +DA:27,1 +DA:28,1 +DA:31,1 +DA:32,1 +DA:35,2 +DA:37,0 +DA:38,0 +LF:20 +LH:18 +end_of_record \ No newline at end of file diff --git a/lib/src/configuration/coverage.dart b/lib/src/configuration/coverage.dart index bc2eb19..26f1495 100644 --- a/lib/src/configuration/coverage.dart +++ b/lib/src/configuration/coverage.dart @@ -36,6 +36,16 @@ class FileCoverage { } return false; } + + /// Returns true if a [line] is instrumented and executed + bool lineIsCovered(int line) { + return lineIsInstrumented(line) && coverage[line]! > 0; + } + + /// Returns true if a [line] is instrumented + bool lineIsInstrumented(int line) { + return coverage.containsKey(line); + } } /// This class holds the line coverage information for each source code file @@ -58,7 +68,8 @@ class ProjectLineCoverage { return null; } - /// Checks if the lines in [file] are covered by tests. + /// Checks if the lines in [file] are covered by tests. This method should be used + /// to determine if a mutation has to be tested. /// /// Returns true if there is no coverage information for the given file. bool isCoveredByTests(String file, int lineStart, [int lineEnd = -1]) { @@ -70,6 +81,24 @@ class ProjectLineCoverage { return true; } + /// Checks if the [line] in [file] is instrumented and covered. + bool lineIsCovered(String file, int line) { + var info = getFileOrNull(file); + if (info != null) { + return info.lineIsCovered(line); + } + return false; + } + + /// Checks if the [line] in [file] is instrumented. + bool lineIsInstrumented(String file, int line) { + var info = getFileOrNull(file); + if (info != null) { + return info.lineIsInstrumented(line); + } + return false; + } + ProjectLineCoverage() : coveredFiles = {}; /// Creates the class by parsing a given string with the lcov file contents. diff --git a/lib/src/mutation_test.dart b/lib/src/mutation_test.dart index d8a5c12..5577542 100644 --- a/lib/src/mutation_test.dart +++ b/lib/src/mutation_test.dart @@ -113,7 +113,7 @@ class MutationTest { addBuiltin: builtinRules, useDefaultConfig: true); } - createReport(reporter, outputPath, format); + createReport(reporter, outputPath, format, coverageData); return foundAll; } diff --git a/lib/src/reports/create_report.dart b/lib/src/reports/create_report.dart index a699b62..2e26557 100644 --- a/lib/src/reports/create_report.dart +++ b/lib/src/reports/create_report.dart @@ -1,3 +1,4 @@ +import 'package:mutation_test/src/configuration/coverage.dart'; import 'package:mutation_test/src/reports/command_line_report.dart'; import 'package:mutation_test/src/reports/html_report.dart'; import 'package:mutation_test/src/reports/markdown_report.dart'; @@ -8,7 +9,10 @@ import 'package:mutation_test/src/reports/xunit_report.dart'; /// Creates the test report in directory [outputPath] /// in the specified [format] using the [results]. -void createReport(ReportData results, String outputPath, ReportFormat format) { +/// If the [coverage] is provided, then html reports will show the instrumented and +/// executed lines in the report. +void createReport(ReportData results, String outputPath, ReportFormat format, + [ProjectLineCoverage? coverage]) { writeCommandLineReport(results, results.system); results.sort(); switch (format) { @@ -21,7 +25,7 @@ void createReport(ReportData results, String outputPath, ReportFormat format) { writeMarkdownReport(outputPath, results, results.system); break; case ReportFormat.HTML: - writeHTMLReport(outputPath, results, results.system); + writeHTMLReport(outputPath, results, results.system, coverage); break; case ReportFormat.XUNIT: writeXUnitReport(outputPath, results, results.system); @@ -32,7 +36,7 @@ void createReport(ReportData results, String outputPath, ReportFormat format) { case ReportFormat.ALL: writeXMLReport(outputPath, results, results.system); writeMarkdownReport(outputPath, results, results.system); - writeHTMLReport(outputPath, results, results.system); + writeHTMLReport(outputPath, results, results.system, coverage); writeXUnitReport(outputPath, results, results.system); writeJUnitReport(outputPath, results, results.system); break; diff --git a/lib/src/reports/html_report.dart b/lib/src/reports/html_report.dart index 669868c..2b76c1c 100644 --- a/lib/src/reports/html_report.dart +++ b/lib/src/reports/html_report.dart @@ -2,6 +2,7 @@ // License: BSD-3-Clause // See LICENSE for the full text of the license +import 'package:mutation_test/src/configuration/coverage.dart'; import 'package:mutation_test/src/core/mutated_line.dart'; import 'package:mutation_test/src/reports/file_mutation_results.dart'; import 'package:mutation_test/src/reports/report_data.dart'; @@ -15,13 +16,16 @@ import 'package:mutation_test/src/reports/string_helpers.dart'; /// documents. /// /// [system] is used to make the file system interactions testable. -void writeHTMLReport( - String outPath, ReportData data, SystemInteractions system) { +/// If the [coverage] is provided, then the report will show the instrumented and +/// executed lines in the report. +void writeHTMLReport(String outPath, ReportData data, SystemInteractions system, + [ProjectLineCoverage? coverage]) { final indexContent = createTopLevelHtmlFile(data); final indexName = createReportFileName(defaultReportName(), outPath, 'html'); system.createPathsAndWriteFile(indexName, indexContent); data.testedFiles.forEach((key, value) { - final contents = createSourceHtmlFile(data, value, basename(indexName)); + final contents = + createSourceHtmlFile(data, value, basename(indexName), coverage); final name = createReportFileName(key, outPath, 'html', appendReport: false, removeInputExt: false, @@ -122,13 +126,27 @@ String createMutationList(int line, FileMutationResults file) { return rv.toString(); } +String _getLineNumberFragment(int i, FileMutationResults file, + [ProjectLineCoverage? coverage]) { + var innerStart = ''; + var innerEnd = ''; + if (coverage != null && coverage.lineIsInstrumented(file.path, i)) { + final covered = coverage.lineIsCovered(file.path, i); + innerStart = + covered ? '' : ''; + innerEnd = ''; + } + return '$innerStart${i.toString().padLeft(8)} $innerEnd'; +} + /// Creates the contents of the top level navigation file. /// [reporter] holds the results of the test run that will be formatted to html /// documents. /// [file] holds the data of the current file. /// [topLevelFileName] is used to create a link back to the top level file. String createSourceHtmlFile( - ReportData reporter, FileMutationResults file, String topLevelFileName) { + ReportData reporter, FileMutationResults file, String topLevelFileName, + [ProjectLineCoverage? coverage]) { final rv = StringBuffer(createHtmlFileHeader( reporter, file.path, @@ -142,16 +160,16 @@ String createSourceHtmlFile( var i = 1; for (final src in file.contents.split('\n')) { final line = escapeCharsForHtml(removeNewline(src)); + final number = _getLineNumberFragment(i, file, coverage); if (file.lineHasMutation(i)) { final colorClass = file.lineHasProblem(i) ? 'problem' : 'hit'; rv.write( - ''' + '''
${createMutationList(i, file)}
'''); } else { - rv.write( - '${i.toString().padLeft(8)} $line\n'); + rv.write('$number$line\n'); } ++i; } @@ -334,6 +352,13 @@ String getCSSFileContents() { background-color: #DAE7FE; } +.covered { + background-color: #9EFF83; +} +.instrumented { + background-color: #FF8983; +} + .hit:hover { color: white; background-color: #6688D4; @@ -625,6 +650,12 @@ td.barBg .hit { background-color: #26334F; } + .covered { + background-color: #0A3302; + } + .instrumented { + background-color: #391D1D; + } .addedLine { background-color: rgb(37, 83, 37); } @@ -637,7 +668,6 @@ td.barBg a:link, a:visited { color: #58a6ff; } - .match .tooltip { background-color: black; color: #fff; diff --git a/lib/src/version.dart b/lib/src/version.dart index f1ef17c..ae4806d 100644 --- a/lib/src/version.dart +++ b/lib/src/version.dart @@ -4,5 +4,5 @@ /// Returns the version of this library. String mutationTestVersion() { - return 'mutation-test version: 1.6.1'; + return 'mutation-test version: 1.7.0'; } diff --git a/pubspec.yaml b/pubspec.yaml index 758932c..a3703f5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mutation_test description: A command line application to measure the quality of your test cases by mutating your code and checking if your tests detect the changes. -version: 1.6.1 +version: 1.7.0 homepage: https://domohuhn.github.io/mutation-test/ repository: https://github.com/domohuhn/mutation-test issue_tracker: https://github.com/domohuhn/mutation-test/issues diff --git a/test/core/system_interactions_test.dart b/test/core/system_interactions_test.dart index dee78a6..2adfe6c 100644 --- a/test/core/system_interactions_test.dart +++ b/test/core/system_interactions_test.dart @@ -59,7 +59,7 @@ void main() { test('list directory', () { var sys = SystemInteractions(true, false); final list = sys.listDirectoryContents(dir, false, []); - expect(list.length, 9); + expect(list.length, 10); }); test('list directory with pattern', () { diff --git a/test/reports/create_report_test.dart b/test/reports/create_report_test.dart index 94a4428..a13c652 100644 --- a/test/reports/create_report_test.dart +++ b/test/reports/create_report_test.dart @@ -69,8 +69,8 @@ void main() { expect(mock.argPaths[0], 'fake_dir/mutation-test-report.html'); expect(mock.argPaths[1], 'fake_dir/path.dart.html'); expect(mock.argTexts.length, 2); - expect(mock.argTexts[0].length, 7979); - expect(mock.argTexts[1].length, 8853); + expect(mock.argTexts[0].length, 8167); + expect(mock.argTexts[1].length, 9041); }); test('none', () { diff --git a/test/reports/report_data_test.dart b/test/reports/report_data_test.dart index d6f086d..156f0f5 100644 --- a/test/reports/report_data_test.dart +++ b/test/reports/report_data_test.dart @@ -265,8 +265,8 @@ void main() { expect(mock.argPaths[0], 'fake_dir/mutation-test-report.html'); expect(mock.argPaths[1], 'fake_dir/path.dart.html'); expect(mock.argTexts.length, 2); - expect(mock.argTexts[0].length, 7979); - expect(mock.argTexts[1].length, 8853); + expect(mock.argTexts[0].length, 8167); + expect(mock.argTexts[1].length, 9041); }); }); }