Ruby 3.2, You’ll be able to measure coverages of ERB template!! (1) eval, ERB part
2022/09/22, Ruby had got an interesting change for code coverage. Specifically, it seems that code coverage in eval can now be taken, which was not possible before.
You may not sound familiar with “in eval”, but ERB and HAML templates can benefit from these changes because they use “eval”.
Overview
Last week, 2022/09/22, an interesting change for code coverage had been merged into ruby’s master branch.
- 2022/09/22 https://github.com/ruby/ruby/pull/6396
- 2022/09/29 https://github.com/ruby/ruby/commit/9dd902b83186ad6f9d0a553da2ca114bac6ab7b5
With these changes, we may be able to measure coverages in the “eval” program!!
- What we can do now
- measuring coverage in “eval” programs. - Its use cases
- measuring coverage in “View” of Rails.
So, in this post, I will survey the code coverage of “eval” and ERB, and finally, as a practical use case, confirm that the coverage of “View Template” in Rails Application can be measured coverages.
Prerequisite Knowledge
Code Coverage
Code Coverage is the records of which codes were executed. A Common use case includes using it to measure coverages of test code. (“test coverage”)
In addition, “oneshot coverage”, which is implemented in some languages, is useful for detecting dead code.
Ruby offers this feature by Coverage
class as an official library.
Code Coverage in eval
The aforementioned Coverage class hasn’t supported measuring coverage in eval until today.
For example, executing coverage of eval program.
1 def foo(n)
2 if n <= 10
3 p "n < 10"
4 else
5 p "n >= 10"
6 end
7 end
8
9 eval <<~RUBY, nil, __FILE__, __LINE__ + 1
10 def bar(n)
11 if n <= 10
12 p "n < 10"
13 else
14 p "n >= 10"
15 end
16 end
17 RUBY
18
19 foo(1)
20 foo(2)
21 bar(1)
22 bar(2)
This program measured code coverage.
require "coverage"Coverage.start(lines: true, eval: true)
load "lib/foo.rb"p Coverage.result
This result.
"n < 10"
"n < 10"
"n < 10"
"n < 10"
{"lib/boo.rb"=>{:lines=>[1, 2, 2, nil, 0, nil, nil, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1]}
Lines between 10 and 17, coverage cannot be measured coverage.
About “View” of Rails
View Template(ERB, HAML, …) of Rails use “eval” internally.
Also, there is a problem in that the coverage in the “View” cannot be measured due to the coverage nature in the eval as described in the previous section.
Verification
In this verification, we will check the following four points on the latest master branch of ruby. (2022/09/28)
- can measure coverage in “eval”. (this post)
- can measure coverage in “ERB” template. (this post)
- can measure coverage in the “View” of the Rails application. (next post)
- ERB
- HAML
In addition, this post will introduce 1 and 2, and 3 will be explained in the next post.
There are codes used for verification.
https://github.com/igsr5/inspection-of-latest-ruby-for-eval-coverage
Verification environment
In this verification, we will use two versions of ruby, 3.1.0
and 2022-09-28 master
to compare results.
1. can measure coverage in “eval”
This is the simple eval program we mentioned earlier.
we expect this verification to measure coverage in “eval” when only 2022–09–28 master
.
1 def foo(n)
2 if n <= 10
3 p "n < 10"
4 else
5 p "n >= 10"
6 end
7 end
8
9 eval <<~RUBY, nil, __FILE__, __LINE__ + 1
10 def bar(n)
11 if n <= 10
12 p "n < 10"
13 else
14 p "n >= 10"
15 end
16 end
17 RUBY
18
19 foo(1)
20 foo(2)
21 bar(1)
22 bar(2)
This program measured code coverage. Note that eval
mode has been added.
require "coverage"Coverage.start(lines: true, eval: true)
load "lib/foo.rb"p Coverage.result
This result.
Case 3.1.0
`:
"n < 10"
"n < 10"
"n < 10"
"n < 10"
{"lib/boo.rb"=>{:lines=>[1, 2, 2, nil, 0, nil, nil, nil, 1, nil, nil, nil, nil, nil, nil, nil, nil, nil, 1, 1, 1, 1]}}
Case 2022-09-28 master
:
"n < 10"
"n < 10"
"n < 10"
"n < 10"
{"lib/boo.rb"=>{:lines=>[1, 2, 2, nil, 0, nil, nil, nil, 1, 1, 2, 2, nil, 0, nil, nil, nil, nil, 1, 1, 1, 1]}}
In 2022-09-28 master
compared to 3.1.0
, we can see the coverage for lines between 10 and 17 has been able to measure.
2. can measure coverage in “ERB” template
Next, the code coverage of using the “ERB” template is verified.
“ERB” class is using “eval” when rendering the template, so it is expected that code coverage in the “ERB” template can be taken as in Verification 1.
Using program.
require "erb"# Build template data class.
class Product
def initialize( code, name, desc, cost )
@code = code
@name = name
@desc = desc
@cost = cost@features = [ ]
enddef add_feature( feature )
@features << feature
end# Support templating of member data.
def get_binding
binding
end# ...
end# Create template.
template = %{
<html>
<head><title>Ruby Toys -- <%= @name %></title></head>
<body><h1><%= @name %> (<%= @code %>)</h1>
<p><%= @desc %></p><ul>
<% @features.each do |f| %>
<li><b><%= f %></b></li>
<% end %>
</ul><p>
<% if @cost < 10 %>
<b>Only <%= @cost %>!!!</b>
<% else %>
Call for a price, today!
<% end %>
</p></body>
</html>
}.gsub(/^ /, '')rhtml = ERB.new(template)# Set up template data.
toy = Product.new( "TZ-1002",
"Rubysapien",
"Geek's Best Friend! Responds to Ruby commands...",
999.95 )
toy.add_feature("Listens for verbal commands in the Ruby language!")
toy.add_feature("Ignores Perl, Java, and all C variants.")
toy.add_feature("Karate-Chop Action!!!")
toy.add_feature("Matz signature on left leg.")
toy.add_feature("Gem studded eyes... Rubies, of course!")# Produce result.
rhtml.run(toy.get_binding)
Coverage.
require "coverage"Coverage.start(lines: true, eval: true)
load "lib/erb_sample.rb"p Coverage.result
This Result.
Case 3.1.0
:
{"lib/erb.rb"=>{:oneshot_lines=>[3, 6, 7, 16, 21, 29, 55, 58, 8, 9, 10, 11, 13, 62, 17, 63, 64, 65, 66, 69, 22]}, "/usr/lib/ruby/2.7.0/erb.rb"=>{:oneshot_lines=>[15, 258, 259, 262, 269, 340, 341, 342, 345, 346, 349, 350, 351, 352, 355, 358, 362, 367, 368, 369, 375, 376, 378, 381, 382, 401, 413, 426, 435, 449, 471, 489, 490, 495, 359, 498, 501, 502, 513, 353, 515, 516, 536, 539, 540, 550, 552, 556, 562, 572, 576, 582, 605, 629, 642, 659, 688, 694, 701, 704, 707, 710, 713, 715, 718, 720, 736, 744, 809, 830, 831, 832, 833, 838, 843, 846, 850, 854, 871, 881, 889, 901, 910, 922, 932, 941, 958, 977, 986, 988, 989, 1002, 1005,1006, 1007, 1021, 1026, 1027, 1028, 1034, 1063, 1064, 1067, 1077, 811, 814, 818, 823, 839, 695, 660, 666, 696, 697, 698, 699, 824, 882, 883, 884, 885, 825, 583, 584, 585, 586, 721, 722, 723, 733, 587, 541, 542, 543, 544, 545, 546, 547, 553, 589, 590, 689, 363, 364, 370, 371, 372, 373, 591, 503, 504, 505, 506, 507, 508, 592, 593, 594, 595, 606, 625, 509, 615, 616, 573, 617, 597, 630, 638, 632, 643, 653, 577, 633, 634, 645, 650, 600, 601, 563, 564, 565, 567, 568, 602, 826, 827, 828, 890, 902, 905]}, "/usr/lib/ruby/2.7.0/cgi/util.rb"=>{:oneshot_lines=>[2, 3, 4, 5, 7, 8, 12, 22, 30, 41, 58, 65, 122, 125, 140, 160, 172, 175, 178, 181, 187, 211, 222]}}
Case 2022-09-28 master
:
{"lib/erb.rb"=>{:oneshot_lines=>[3, 6, 7, 16, 21, 29, 55, 58, 8, 9, 10, 11, 13, 62, 17, 63, 64, 65, 66, 69, 22]}, "/root/.rubies/ruby-master/lib/ruby/3.2.0+2/erb.rb"=>{:oneshot_lines=>[15, 16, 259, 260, 261, 264, 271, 342, 343, 344, 347, 348, 351, 352, 353, 354, 357, 360, 364, 369, 370, 371, 377, 378, 380, 383, 384, 403, 415, 428, 437, 451, 473, 491, 492, 497, 361, 500, 503, 504, 515, 355, 517, 518, 538, 541, 542, 552, 554, 558, 564, 574, 578, 584, 607, 631, 644, 661, 690, 696, 703, 706, 709, 712, 715, 717, 720, 722, 738, 746, 811, 832, 833, 838, 843, 846, 850, 854, 871, 881, 889, 901, 910, 922, 932, 941, 958, 977, 986, 988, 989, 1002, 1005, 1006, 1007, 1021, 1026, 1027, 1028, 1034, 1063, 1064, 1067, 1077, 813, 816, 820, 825, 839, 697, 662, 668, 698, 699, 700, 701, 826, 882, 883, 884, 885, 827, 585, 586, 587, 588, 723, 724, 725, 735, 589, 543, 544, 545, 546, 547, 548, 549, 555, 591, 592, 691, 365, 366, 372, 373, 374, 375, 593, 505, 506, 507, 508, 509, 510, 594, 595, 596, 597, 608, 627, 511, 617, 618, 575, 619, 599, 632, 640, 634, 645, 655, 579, 635, 636, 647, 652, 602, 603, 565, 566, 567, 569, 570, 604, 828, 829, 830, 890, 902, 905]}, "/root/.rubies/ruby-master/lib/ruby/3.2.0+2/cgi/util.rb"=>{:oneshot_lines=>[2, 3, 4, 5, 7, 8, 14, 27, 41, 53, 63, 74, 94, 101, 160, 163, 178, 198, 210, 213, 219, 240, 251]}, "/root/.rubies/ruby- master/lib/ruby/3.2.0+2/erb/version.rb"=>{:oneshot_lines=>[2, 3, 4]}, "(erb)"=>{:oneshot_lines=>[1, 3, 6, 7, 10, 10, 11, 12, 16, 18, 20, 25]
It’s a little confusing, in 2022-09-28 master
compared to 3.1.0
, we can see the coverage for “ERB” template.
Next verification…
In this post, we have verified to be able to measure code coverage for “eval” and “ERB”. Next post, as a more practical use case, we will verify to be able to measure code coverage for “View” template of Rails. In addition, we will use simplecov for the verification.