Skip to content

Update to Crystal 1.0.0+ and better integrate with spec #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
61 changes: 16 additions & 45 deletions src/coverage/inject/source_file.cr
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,6 @@ require "./extensions"
require "./macro_utils"

class Coverage::SourceFile < Crystal::Visitor
# List of keywords which are trouble with variable
# name. Some keywoards are not and won't be present in this
# list.
# Since this can break the code replacing the variable by a underscored
# version of it, and I'm not sure about this list, we will need to add/remove
# stuff to not break the code.
CRYSTAL_KEYWORDS = %w(
abstract do if nil? self unless
alias else of sizeof until
as elsif include struct when
as? end instance_sizeof pointerof super while
asm ensure is_a? private then with
begin enum lib protected true yield
break extend macro require
case false module rescue typeof
class for next return uninitialized
def fun nil select union
)

class_getter file_list = [] of Coverage::SourceFile
class_getter already_covered_file_name = Set(String).new
class_getter! project_path : String
Expand Down Expand Up @@ -74,14 +55,16 @@ class Coverage::SourceFile < Crystal::Visitor
# Inject in AST tree if required.
def process
unless @astree
@astree = Crystal::Parser.parse(self.source)
parser = Crystal::Parser.new(self.source)
parser.filename = File.expand_path(path, ".")
@astree = parser.parse
astree.accept(self)
end
end

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 +86,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 +103,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 +122,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 +154,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 +163,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 @@ -259,26 +245,11 @@ class Coverage::SourceFile < Crystal::Visitor
end

def visit(node : Crystal::Arg)
name = node.name
if CRYSTAL_KEYWORDS.includes?(name)
node.external_name = node.name = "_#{name}"
end

true
end

# Placeholder for bug #XXX
def visit(node : Crystal::Assign)
target = node.target
value = node.value

if target.is_a?(Crystal::InstanceVar) &&
value.is_a?(Crystal::Var)
if CRYSTAL_KEYWORDS.includes?(value.name)
value.name = "_#{value.name}"
end
end

true
end

Expand Down Expand Up @@ -312,7 +283,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><%[email protected]_lines%></td>
<td><%[email protected]_lines%></td>
<td class="low"><%[email protected]_coverage_str%></td>
<td><%[email protected]_lines%> / <%[email protected]_lines%> (<%[email protected]_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>