From 6f1700a1250c1514d90e0ea6a5642ee6010f8dff Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 10:49:34 +0000 Subject: [PATCH 01/18] chore: update ameba --- shard.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shard.lock b/shard.lock index 48428b1..53ba68b 100644 --- a/shard.lock +++ b/shard.lock @@ -1,6 +1,6 @@ -version: 1.0 +version: 2.0 shards: ameba: - github: veelenga/ameba - version: 0.10.0 + git: https://github.com/veelenga/ameba.git + version: 0.14.3 From ab1fa903edbaa6daf5f70d2e2266b73516469141 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 10:50:06 +0000 Subject: [PATCH 02/18] fix: Crystal 1.0.0 --- src/coverage/inject/cli.cr | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coverage/inject/cli.cr b/src/coverage/inject/cli.cr index 9951535..12c3702 100644 --- a/src/coverage/inject/cli.cr +++ b/src/coverage/inject/cli.cr @@ -1,4 +1,5 @@ require "option_parser" + # require "tempfile" module Coverage @@ -8,11 +9,15 @@ module Coverage filenames = [] of String print_only = false - OptionParser.parse! do |parser| - parser.banner = "Usage: crystal-cover [options] " + OptionParser.parse do |parser| + parser.banner = "Usage: crystal-coverage [options] " 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 From 1e87b18a71522ff319c4c408dcd1f406c50b2b76 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 11:26:48 +0000 Subject: [PATCH 03/18] fix: Crystal 1.0.0 spec runs at exit --- src/coverage/inject/source_file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index b15fc92..bb362ac 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -139,7 +139,7 @@ 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. From ff998ffc3b083e4ca9fa108e6915636854befe3a Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 11:27:08 +0000 Subject: [PATCH 04/18] feat: integrate with spec files by default --- src/coverage/inject/cli.cr | 4 ++-- src/coverage/inject/source_file.cr | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coverage/inject/cli.cr b/src/coverage/inject/cli.cr index 12c3702..ca4cad2 100644 --- a/src/coverage/inject/cli.cr +++ b/src/coverage/inject/cli.cr @@ -25,12 +25,12 @@ module Coverage 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 diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index bb362ac..75e888b 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -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 @@ -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" @@ -177,7 +177,7 @@ 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))) end From 1c526cea10685daa285ab982548dafde355e88f6 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 11:27:18 +0000 Subject: [PATCH 05/18] fix(makefile): check for changes --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 62c9c6b..18bf9d5 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ PREFIX ?= /usr/local SHARD_BIN ?= ../../bin build: bin/crystal-coverage -bin/crystal-coverage: +bin/crystal-coverage: $(shell find src -type f -name '*.cr') $(SHARDS_BIN) build $(CRFLAGS) clean: rm -f .bin/crystal-coverage .bin/crystal-coverage.dwarf @@ -16,4 +16,4 @@ bin: build cp ./bin/crystal-coverage $(SHARD_BIN) # test: build # $(CRYSTAL_BIN) spec -# ./bin/crystal-coverage +# ./bin/crystal-coverage From d8c851c887bb95dc4b5756d487945bfebf631902 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 11:29:08 +0000 Subject: [PATCH 06/18] feat: adjust output to prepare for branches and functions --- src/coverage/runtime/outputters/html_report.cr | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/coverage/runtime/outputters/html_report.cr b/src/coverage/runtime/outputters/html_report.cr index a90fc2d..9e29b0f 100644 --- a/src/coverage/runtime/outputters/html_report.cr +++ b/src/coverage/runtime/outputters/html_report.cr @@ -1,6 +1,7 @@ require "ecr" require "file_utils" require "html" +require "../coverage" class Coverage::Outputter::HtmlReport < Coverage::Outputter struct CoverageReport @@ -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 @@ -71,8 +72,6 @@ class Coverage::Outputter::HtmlReport < Coverage::Outputter end def output(files : Array(Coverage::File)) - puts "Generating coverage report, please wait..." - system("rm -r coverage/") sum_lines = 0 @@ -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") From 84bb8d7b9cdf2cbd754880e402408668b346c363 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sat, 21 Aug 2021 11:30:20 +0000 Subject: [PATCH 07/18] chore: improve readme for running spec --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 552261c..826fe6b 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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? From 040ee462abe042d42ba1664759d390e74ff27ec1 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 Aug 2021 11:17:02 +0000 Subject: [PATCH 08/18] fix: don't install development dependencies without need --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 18bf9d5..b3161a9 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ SHARD_BIN ?= ../../bin build: bin/crystal-coverage bin/crystal-coverage: $(shell find src -type f -name '*.cr') - $(SHARDS_BIN) build $(CRFLAGS) + $(SHARDS_BIN) --without-development build $(CRFLAGS) clean: rm -f .bin/crystal-coverage .bin/crystal-coverage.dwarf install: build From b9c827178e99a4aa0a79780acf645f996baf0b00 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 Aug 2021 11:36:06 +0000 Subject: [PATCH 09/18] fix: remove 'unsafe_as' --- src/coverage/inject/source_file.cr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index 75e888b..50608be 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -168,7 +168,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 @@ -179,7 +179,7 @@ class Coverage::SourceFile < Crystal::Visitor location ||= node.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) From e43caa20e1593b1a2c22a7d573c9cac8841b8f66 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 Aug 2021 11:58:47 +0000 Subject: [PATCH 10/18] fix: adding `;` might break parsing on some cases, fix it. --- src/coverage/inject/source_file.cr | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index 50608be..06fed1b 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -144,12 +144,15 @@ class Coverage::SourceFile < Crystal::Visitor # 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("") From 37d496afb09a4fd55c2345b59c9955f3da9ee95f Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 Aug 2021 12:04:01 +0000 Subject: [PATCH 11/18] feat: simplify html output for "lines" --- template/cover.html.ecr | 8 ++------ template/summary.html.ecr | 16 ++++++---------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/template/cover.html.ecr b/template/cover.html.ecr index 212ba3f..7db4a15 100644 --- a/template/cover.html.ecr +++ b/template/cover.html.ecr @@ -94,15 +94,11 @@
- - - + - - - +
Hitted linesRelevant linesPercentageLines
<%=@file.relevant_lines%><%=@file.covered_lines%><%=@file.percent_coverage_str%><%=@file.covered_lines%> / <%=@file.relevant_lines%> (<%=@file.percent_coverage_str%>)
diff --git a/template/summary.html.ecr b/template/summary.html.ecr index f3bac36..c242e46 100644 --- a/template/summary.html.ecr +++ b/template/summary.html.ecr @@ -32,26 +32,22 @@ - - - + <%- @covered_files.each do |file| -%> - - - + + + <%- end -%> - - - +
FileRelevant linesCovered linesPercentage coveredLines
<%=file.filename%><%=file.relevant_lines%><%=file.covered_lines%><%=file.percent_coverage_str%><%=file.covered_lines%> / <%=file.relevant_lines%> (<%=file.percent_coverage_str%>)
TOTAL: <%= total_relevant %><%= total_covered %><%= total_percentage %><%= total_covered %> / <%= total_relevant %> (<%= total_percentage %>)
- \ No newline at end of file + From 4148e9e52bcf2b02fd529013855aa5dc1d12db4d Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Mon, 23 Aug 2021 12:21:44 +0000 Subject: [PATCH 12/18] chore: update shard.yml with Crystal version restriction and correct ameba dependency --- shard.lock | 2 +- shard.yml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/shard.lock b/shard.lock index 53ba68b..1e8513e 100644 --- a/shard.lock +++ b/shard.lock @@ -1,6 +1,6 @@ version: 2.0 shards: ameba: - git: https://github.com/veelenga/ameba.git + git: https://github.com/crystal-ameba/ameba.git version: 0.14.3 diff --git a/shard.yml b/shard.yml index edab69a..8a1bc3c 100644 --- a/shard.yml +++ b/shard.yml @@ -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 From 99c7493d6e29ac75d9cadc4468c44dd2ee3a0a4a Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 May 2022 14:17:23 -0300 Subject: [PATCH 13/18] fix: inject full path as location comments --- src/coverage/inject/source_file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index 06fed1b..a3e6520 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -120,7 +120,7 @@ class Coverage::SourceFile < Crystal::Visitor end private def inject_location(file = @path, line = 0, column = 0) - %(#) + %(#) end def self.prelude_operations From ae99f75494201e6358f7f26b3ec523af9a3de62d Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 May 2022 14:22:46 -0300 Subject: [PATCH 14/18] fix: remove coverage folder only if it exists --- src/coverage/runtime/outputters/html_report.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverage/runtime/outputters/html_report.cr b/src/coverage/runtime/outputters/html_report.cr index 9e29b0f..4a705d8 100644 --- a/src/coverage/runtime/outputters/html_report.cr +++ b/src/coverage/runtime/outputters/html_report.cr @@ -72,7 +72,7 @@ class Coverage::Outputter::HtmlReport < Coverage::Outputter end def output(files : Array(Coverage::File)) - system("rm -r coverage/") + system("rm -rf coverage/") sum_lines = 0 sum_covered = 0 From bff4ddc5d1a414d01161d5bd6a2aa3d15ac6a6bf Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 May 2022 14:34:41 -0300 Subject: [PATCH 15/18] fix: don't cover the 'else' branch of {% begin %} --- src/coverage/inject/source_file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index a3e6520..988ff40 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -315,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 == BoolLiteral.new(true) true end From 0a435b8fd941a80c17aa880977ac2f7ed77e4362 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Sun, 22 May 2022 14:37:27 -0300 Subject: [PATCH 16/18] fix: typo on constant namespace --- src/coverage/inject/source_file.cr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index 988ff40..a7e12e6 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -315,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) unless node.cond == BoolLiteral.new(true) + node.else = force_inject_cover(node.else) unless node.cond == Crystal::BoolLiteral.new(true) true end From 0eacb052195919ae653510912edfed7a620b7af5 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Mon, 23 May 2022 08:26:04 -0300 Subject: [PATCH 17/18] fix: correctly handle __DIR__ and __FILE__ constants --- src/coverage/inject/source_file.cr | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index a7e12e6..f7ba145 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -74,7 +74,9 @@ 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 From eb69ebac0fb63461851f098c83201da45f60bb76 Mon Sep 17 00:00:00 2001 From: Guilherme Bernal Date: Mon, 23 May 2022 08:27:03 -0300 Subject: [PATCH 18/18] fix: stop replacing CRYSTAL_KEYWORDS --- src/coverage/inject/source_file.cr | 34 ------------------------------ 1 file changed, 34 deletions(-) diff --git a/src/coverage/inject/source_file.cr b/src/coverage/inject/source_file.cr index f7ba145..1644512 100644 --- a/src/coverage/inject/source_file.cr +++ b/src/coverage/inject/source_file.cr @@ -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 @@ -264,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