Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ PREFIX ?= /usr/local
SHARD_BIN ?= ../../bin

build: bin/crystal-coverage
bin/crystal-coverage:
$(SHARDS_BIN) build $(CRFLAGS)
bin/crystal-coverage: $(shell find src -type f -name '*.cr')
$(SHARDS_BIN) --without-development build $(CRFLAGS)
clean:
rm -f .bin/crystal-coverage .bin/crystal-coverage.dwarf
install: build
Expand All @@ -16,4 +16,4 @@ bin: build
cp ./bin/crystal-coverage $(SHARD_BIN)
# test: build
# $(CRYSTAL_BIN) spec
# ./bin/crystal-coverage
# ./bin/crystal-coverage
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Wait for the binary to compile. The binary will be build in `bin/crystal-coverag
## Usage

```
crystal-coverage spec/myfile_spec1.cr spec/myfile_spec2.cr
bin/crystal-coverage
```

Coverage file will be recreated after your software run on `coverage/` folder.
Expand Down Expand Up @@ -60,7 +60,7 @@ software is executed without release flag.
To test in `--release` mode, you can do:

```
crystal-coverage src/main.cr -p | crystal eval --release
bin/crystal-coverage -p | crystal eval --release
```

## How does it works?
Expand Down
6 changes: 3 additions & 3 deletions shard.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version: 1.0
version: 2.0
shards:
ameba:
github: veelenga/ameba
version: 0.10.0
git: https://github.com/crystal-ameba/ameba.git
version: 0.14.3

4 changes: 3 additions & 1 deletion shard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ targets:
crystal-coverage:
main: src/coverage/cli.cr

crystal: ">= 1.0.0, < 2.0.0"

scripts:
postinstall: make bin

development_dependencies:
ameba:
github: veelenga/ameba
github: crystal-ameba/ameba

license: MIT
13 changes: 9 additions & 4 deletions src/coverage/inject/cli.cr
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "option_parser"

# require "tempfile"

module Coverage
Expand All @@ -8,24 +9,28 @@ module Coverage
filenames = [] of String
print_only = false

OptionParser.parse! do |parser|
parser.banner = "Usage: crystal-cover [options] <filename>"
OptionParser.parse do |parser|
parser.banner = "Usage: crystal-coverage [options] <filename>"
parser.on("-o FORMAT", "--output-format=FORMAT", "The output format used (default: HtmlReport): HtmlReport, Coveralls ") { |f| output_format = f }
parser.on("-p", "--print-only", "output the generated source code") { |_p| print_only = true }
parser.on("--use-require=REQUIRE", "change the require of cover library in runtime") { |r| Coverage::SourceFile.use_require = r }
parser.on("-h", "--help", "Show this help") do
puts parser
exit
end
parser.unknown_args do |args|
args.each do
filenames << ARGV.shift
end
end
end

raise "You must choose a file to compile" unless filenames.any?
filenames = Dir["spec/**/*_spec.cr"] unless filenames.any?

Coverage::SourceFile.outputter = "Coverage::Outputter::#{output_format.camelcase}"

first = true
output = String::Builder.new(capacity: 2**18)
output = String::Builder.new
filenames.each do |f|
v = Coverage::SourceFile.new(path: f, source: ::File.read(f))
output << v.to_covered_source
Expand Down
23 changes: 13 additions & 10 deletions src/coverage/inject/source_file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ class Coverage::SourceFile < Crystal::Visitor

def to_covered_source
if @enriched_source.nil?
io = String::Builder.new(capacity: 32_768)
io = String::Builder.new

# call process to enrich AST before
# injection of cover head dependencies
Expand All @@ -103,7 +103,7 @@ class Coverage::SourceFile < Crystal::Visitor
file_list = @@require_expanders[expansion_id]

if file_list.any?
io = String::Builder.new(capacity: (2 ** 20))
io = String::Builder.new
file_list.each do |file|
io << "#" << "require of `" << file.path
io << "` from `" << self.path << ":#{file.required_at}" << "`" << "\n"
Expand All @@ -120,7 +120,7 @@ class Coverage::SourceFile < Crystal::Visitor
end

private def inject_location(file = @path, line = 0, column = 0)
%(#<loc:"#{file}",#{[line, 0].max},#{[column, 0].max}>)
%(#<loc:"#{File.expand_path(file, ".")}",#{[line, 0].max},#{[column, 0].max}>)
end

def self.prelude_operations
Expand All @@ -139,17 +139,20 @@ class Coverage::SourceFile < Crystal::Visitor
end

def self.final_operations
"\n::Coverage.get_results(#{@@outputter}.new)"
"\n Spec.after_suite { ::Coverage.get_results(#{@@outputter}.new) }"
end

# Inject line tracer for easy debugging.
# add `;` after the Coverage instrumentation
# to avoid some with macros
# to avoid some with macros. Be careful to only insert
# `;` if there is something else on the same line, or else
# it breaks parsing with expressions inside expressions.
private def inject_line_traces(output)
output.gsub(/\:\:Coverage\[([0-9]+),[ ]*([0-9]+)\](.*)/) do |_str, match|
[
"::Coverage[", match[1],
", ", match[2], "]; ",
", ", match[2], "]",
match[3].empty? ? " " : "; ",
match[3],
inject_location(@path, @lines[match[2].to_i] - 1),
].join("")
Expand All @@ -168,7 +171,7 @@ class Coverage::SourceFile < Crystal::Visitor

n = Crystal::Call.new(Crystal::Global.new("::Coverage"), "[]",
[Crystal::NumberLiteral.new(@id),
Crystal::NumberLiteral.new(lidx)].unsafe_as(Array(Crystal::ASTNode)))
Crystal::NumberLiteral.new(lidx)] of Crystal::ASTNode)
n
else
node
Expand All @@ -177,9 +180,9 @@ class Coverage::SourceFile < Crystal::Visitor

private def force_inject_cover(node : Crystal::ASTNode, location = nil)
location ||= node.location
return node if @already_covered_locations.includes?(location)
return node if @already_covered_locations.includes?(location) || @path.starts_with? "spec/"
already_covered_locations << location
Crystal::Expressions.from([inject_coverage_tracker(node), node].unsafe_as(Array(Crystal::ASTNode)))
Crystal::Expressions.from([inject_coverage_tracker(node), node] of Crystal::ASTNode)
end

def inject_cover(node : Crystal::ASTNode)
Expand Down Expand Up @@ -312,7 +315,7 @@ class Coverage::SourceFile < Crystal::Visitor
propagate_location_in_macro(node, node.location.not_nil!)

node.then = force_inject_cover(node.then)
node.else = force_inject_cover(node.else)
node.else = force_inject_cover(node.else) unless node.cond == Crystal::BoolLiteral.new(true)
true
end

Expand Down
15 changes: 6 additions & 9 deletions src/coverage/runtime/outputters/html_report.cr
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require "ecr"
require "file_utils"
require "html"
require "../coverage"

class Coverage::Outputter::HtmlReport < Coverage::Outputter
struct CoverageReport
Expand All @@ -22,7 +23,7 @@ class Coverage::Outputter::HtmlReport < Coverage::Outputter
end

def percent_coverage_str
"#{(100*percent_coverage).round(2)}%"
"#{"%.2f" % (100*percent_coverage)}%"
end
end

Expand Down Expand Up @@ -71,9 +72,7 @@ class Coverage::Outputter::HtmlReport < Coverage::Outputter
end

def output(files : Array(Coverage::File))
puts "Generating coverage report, please wait..."

system("rm -r coverage/")
system("rm -rf coverage/")

sum_lines = 0
sum_covered = 0
Expand Down Expand Up @@ -107,14 +106,12 @@ class Coverage::Outputter::HtmlReport < Coverage::Outputter
sum_covered += cr.covered_lines

cr
end.select do |cr|
cr.relevant_lines > 0
end

# puts percent covered
if sum_lines == 0
puts "100% covered"
else
puts (100.0*(sum_covered / sum_lines.to_f)).round(2).to_s + "% covered"
end
print "\nLines #{sum_lines == 0 ? 100 : "%.2f" % (100 * sum_covered / sum_lines)}% covered"

# Generate the code
FileUtils.mkdir_p("coverage")
Expand Down
8 changes: 2 additions & 6 deletions template/cover.html.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -94,15 +94,11 @@
<hr>
<table class="cover-table">
<thead>
<th>Hitted lines</th>
<th>Relevant lines</th>
<th>Percentage</th>
<th>Lines</th>
</thead>
<tbody>
<tr>
<td><%=@file.relevant_lines%></td>
<td><%=@file.covered_lines%></td>
<td class="low"><%=@file.percent_coverage_str%></td>
<td><%=@file.covered_lines%> / <%=@file.relevant_lines%> (<%=@file.percent_coverage_str%>)</td>
</tr>
</tbody>
</table>
Expand Down
16 changes: 6 additions & 10 deletions template/summary.html.ecr
Original file line number Diff line number Diff line change
Expand Up @@ -32,26 +32,22 @@
<table>
<thead>
<th>File</th>
<th>Relevant lines</th>
<th>Covered lines</th>
<th>Percentage covered</th>
<th>Lines</th>
</thead>
<tbody>
<%- @covered_files.each do |file| -%>
<tr>
<td><a href="<%=file.md5%>.html"><%=file.filename%></a></td>
<td><%=file.relevant_lines%></td>
<td><%=file.covered_lines%></td>
<td><%=file.percent_coverage_str%></td>
<td><%=file.covered_lines%> / <%=file.relevant_lines%> (<%=file.percent_coverage_str%>)</td>
<td></td>
<td></td>
</tr>
<%- end -%>
<tfoot>
<th>TOTAL: </th>
<th><%= total_relevant %></th>
<th><%= total_covered %></th>
<th><%= total_percentage %></th>
<th><%= total_covered %> / <%= total_relevant %> (<%= total_percentage %>)</th>
</tfoot>
</tbody>
</table>
</body>
</html>
</html>