Skip to content

Commit

Permalink
Always floor coverage instead of rounding (#310)
Browse files Browse the repository at this point in the history
* Always floor coverage instead of rounding

We do not want to report a 100% coverage when there are lines
that are not covered.

* Add option to restore previous ceil coverage behaviour
  • Loading branch information
albertored authored Oct 10, 2023
1 parent 719d364 commit c76d8ed
Show file tree
Hide file tree
Showing 9 changed files with 41 additions and 54 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,9 @@ to `false`:
- When set to a number greater than 0, this setting causes the `mix coveralls` and `mix coveralls.html` tasks to exit with a status code of 1 if test coverage falls below the specified threshold (defaults to 0). This is useful to interrupt CI pipelines with strict code coverage rules. Should be expressed as a number between 0 and 100 signifying the minimum percentage of lines covered.
- `html_filter_full_covered`
- A boolean, when `true` files with 100% coverage are not shown in the HTML report. Default to `false`.
- `floor_coverage`
- A boolean, when `false` coverage values are ceiled instead of floored, this means that a project with some lines
that are not covered can still have a total 100% coverage. Default to `true`.
Example configuration file:
Expand Down
3 changes: 2 additions & 1 deletion lib/conf/coveralls.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"coverage_options": {
"treat_no_relevant_lines_as_covered": false,
"output_dir": "cover/",
"minimum_coverage": 0
"minimum_coverage": 0,
"floor_coverage": true
},

"terminal_options": {
Expand Down
27 changes: 5 additions & 22 deletions lib/excoveralls/local.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ defmodule ExCoveralls.Local do
@moduledoc """
Locally displays the result to screen.
"""



defmodule Count do
@moduledoc """
Expand Down Expand Up @@ -102,7 +100,7 @@ defmodule ExCoveralls.Local do
Enum.map(count_info, fn original ->
[stat, count] = original
%{
"cov" => get_coverage(count),
"cov" => ExCoveralls.Stats.get_coverage(count.relevant, count.covered),
"file" => stat[:name],
"lines" => count.lines,
"relevant" => count.relevant,
Expand Down Expand Up @@ -145,16 +143,16 @@ defmodule ExCoveralls.Local do
end

defp format_info([stat, count]) do
coverage = get_coverage(count)
coverage = ExCoveralls.Stats.get_coverage(count.relevant, count.covered)
file_width = ExCoveralls.Settings.get_file_col_width
print_string("~5.1f% ~-#{file_width}s ~8w ~8w ~8w",
print_string("~5w% ~-#{file_width}s ~8w ~8w ~8w",
[coverage, stat[:name], count.lines, count.relevant, count.relevant - count.covered])
end

defp format_total(info) do
totals = Enum.reduce(info, %Count{}, fn([_, count], acc) -> append(count, acc) end)
coverage = get_coverage(totals)
print_string("[TOTAL] ~5.1f%", [coverage])
coverage = ExCoveralls.Stats.get_coverage(totals.relevant, totals.covered)
print_string("[TOTAL] ~5w%", [coverage])
end

defp append(a, b) do
Expand All @@ -165,21 +163,6 @@ defmodule ExCoveralls.Local do
}
end

defp get_coverage(count) do
case count.relevant do
0 -> default_coverage_value()
_ -> (count.covered / count.relevant) * 100
end
end

defp default_coverage_value do
options = ExCoveralls.Settings.get_coverage_options
case Map.fetch(options, "treat_no_relevant_lines_as_covered") do
{:ok, false} -> 0.0
_ -> 100.0
end
end

@doc """
Calculate count information from the coverage stats.
"""
Expand Down
8 changes: 4 additions & 4 deletions lib/excoveralls/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ defmodule ExCoveralls.Settings do
Get default coverage value for lines marked as not relevant.
"""
def default_coverage_value do
case Map.fetch(get_coverage_options(), "treat_no_relevant_lines_as_covered") do
{:ok, true} -> 100.0
_ -> 0.0
end
get_coverage_options() |> default_coverage_value()
end

def default_coverage_value(%{"treat_no_relevant_lines_as_covered" => true}), do: 100.0
def default_coverage_value(_), do: 0.0

@doc """
Get terminal output options from the json file.
"""
Expand Down
19 changes: 10 additions & 9 deletions lib/excoveralls/stats.ex
Original file line number Diff line number Diff line change
Expand Up @@ -198,16 +198,17 @@ defmodule ExCoveralls.Stats do
{s+sloc, h+hits, m+misses}
end

defp get_coverage(relevant, covered) do
value = case relevant do
0 -> Settings.default_coverage_value
_ -> (covered / relevant) * 100
def get_coverage(relevant, covered) do
coverage_options = Settings.get_coverage_options()

approximate_fn = case coverage_options do
%{"floor_coverage" => false} -> &Float.round(&1, 1)
_ -> &Float.floor(&1, 1)
end

if value == trunc(value) do
trunc(value)
else
Float.round(value, 1)
case relevant do
0 -> Settings.default_coverage_value(coverage_options)
_ -> approximate_fn.((covered / relevant) * 100)
end
end

Expand All @@ -226,7 +227,7 @@ defmodule ExCoveralls.Stats do
Exit the process with a status of 1 if coverage is below the minimum.
"""
def ensure_minimum_coverage(stats) do
coverage_options = ExCoveralls.Settings.get_coverage_options
coverage_options = Settings.get_coverage_options
minimum_coverage = coverage_options["minimum_coverage"] || 0
if minimum_coverage > 0, do: check_coverage_threshold(stats, minimum_coverage)
end
Expand Down
6 changes: 3 additions & 3 deletions test/html_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule ExCoveralls.HtmlTest do
alias ExCoveralls.Html

@file_name "excoveralls.html"
@file_size 20375
@file_size 20381
@test_output_dir "cover_test/"
@test_template_path "lib/templates/html/htmlcov/"

Expand Down Expand Up @@ -35,7 +35,7 @@ defmodule ExCoveralls.HtmlTest do
File.rm!(path)
File.rmdir!(@test_output_dir)
end

ExCoveralls.ConfServer.clear()
end

Expand Down Expand Up @@ -78,7 +78,7 @@ defmodule ExCoveralls.HtmlTest do
output = capture_io(fn ->
assert catch_exit(Html.execute(@source_info)) == {:shutdown, 1}
end)
assert String.contains?(output, "FAILED: Expected minimum coverage of 100%, got 50%.")
assert String.contains?(output, "FAILED: Expected minimum coverage of 100%, got 50.0%.")
end

test_with_mock "Exit status code is 0 when actual coverage reaches the minimum",
Expand Down
11 changes: 5 additions & 6 deletions test/local_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ defmodule ExCoveralls.LocalTest do
"\e[31mdefmodule Test do\e[m\n\e[32m def test do\e[m\n" <>
" end\n" <>
"end"

setup do
ExCoveralls.ConfServer.clear()
on_exit(fn -> ExCoveralls.ConfServer.clear() end)
Expand Down Expand Up @@ -75,11 +75,11 @@ defmodule ExCoveralls.LocalTest do
end

test "Empty (no relevant lines) file is calculated as 0.0%" do
assert String.contains?(Local.coverage(@empty_source_info), "[TOTAL] 100.0%")
assert String.contains?(Local.coverage(@empty_source_info), "[TOTAL] 0.0%")
end

test_with_mock "Empty (no relevant lines) file with treat_no_relevant_lines_as_covered=true option is calculated as 100.0%",
ExCoveralls.Settings, [
ExCoveralls.Settings, [:passthrough], [
get_coverage_options: fn -> %{"treat_no_relevant_lines_as_covered" => true} end,
get_file_col_width: fn -> 40 end,
get_print_files: fn -> true end
Expand All @@ -88,7 +88,7 @@ defmodule ExCoveralls.LocalTest do
end

test_with_mock "Empty (no relevant lines) file with treat_no_relevant_lines_as_covered=false option is calculated as 0.0%",
ExCoveralls.Settings, [
ExCoveralls.Settings, [:passthrough], [
get_coverage_options: fn -> %{"treat_no_relevant_lines_as_covered" => false} end,
get_file_col_width: fn -> 40 end,
get_print_files: fn -> true end
Expand All @@ -97,7 +97,6 @@ defmodule ExCoveralls.LocalTest do
end

test_with_mock "Exit status code is 1 when actual coverage does not reach the minimum",

ExCoveralls.Settings, [
get_coverage_options: fn -> %{"minimum_coverage" => 100} end,
get_file_col_width: fn -> 40 end,
Expand All @@ -107,7 +106,7 @@ defmodule ExCoveralls.LocalTest do
output = capture_io(fn ->
assert catch_exit(Local.execute(@source_info)) == {:shutdown, 1}
end)
assert String.contains?(output, "FAILED: Expected minimum coverage of 100%, got 50%.")
assert String.contains?(output, "FAILED: Expected minimum coverage of 100%, got 50.0%.")
end

test_with_mock "Exit status code is 0 when actual coverage reaches the minimum",
Expand Down
14 changes: 7 additions & 7 deletions test/poster_test.exs
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
defmodule PosterTest do
use ExUnit.Case
import ExUnit.CaptureIO

setup do
bypass = Bypass.open()
%{bypass: bypass, endpoint: "http://localhost:#{bypass.port}"}
end

test "successfully posting JSON", %{bypass: bypass, endpoint: endpoint} do
Bypass.expect(bypass, fn conn ->
assert conn.method == "POST"
assert {"host", "localhost"} in conn.req_headers
Plug.Conn.resp(conn, 200, "")
end)

assert capture_io(fn ->
ExCoveralls.Poster.execute("{}", endpoint: endpoint)
end) =~ "Successfully uploaded"
end

test "post JSON fails", %{bypass: bypass, endpoint: endpoint} do
Bypass.down(bypass)

assert_raise ExCoveralls.ReportUploadError, fn ->
ExCoveralls.Poster.execute("{}", endpoint: endpoint)
end
Expand All @@ -32,7 +32,7 @@ defmodule PosterTest do
assert conn.method == "POST"
Plug.Conn.resp(conn, 500, "")
end)

assert capture_io(fn ->
assert ExCoveralls.Poster.execute("{}", endpoint: endpoint) == :ok
end) =~ ~r/internal server error/
Expand All @@ -43,7 +43,7 @@ defmodule PosterTest do
assert conn.method == "POST"
Plug.Conn.resp(conn, 405, "")
end)

assert capture_io(fn ->
assert ExCoveralls.Poster.execute("{}", endpoint: endpoint) == :ok
end) =~ ~r/maintenance/
Expand All @@ -58,7 +58,7 @@ defmodule PosterTest do

Bypass.expect_once(bypass, "POST", "/api/v1/jobs", fn conn ->
conn
|> Plug.Conn.put_resp_header("location", Path.join(endpoint, "redirected") |> IO.inspect())
|> Plug.Conn.put_resp_header("location", Path.join(endpoint, "redirected"))
|> Plug.Conn.resp(302, "")
end)

Expand Down
4 changes: 2 additions & 2 deletions test/stats_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -125,15 +125,15 @@ defmodule ExCoveralls.StatsTest do
end

test_with_mock "Empty (no relevant lines) file with treat_no_relevant_lines_as_covered option is calculated as 100.0%",
ExCoveralls.Settings, [default_coverage_value: fn -> 100 end] do
ExCoveralls.Settings, [:passthrough], [default_coverage_value: fn _ -> 100 end] do

results = Stats.source(@empty_source_info)
assert(results.coverage == 100)
end

test "coverage stats are rounded to one decimal place" do
results = Stats.source(@fractional_source_info)
assert(results.coverage == 66.7)
assert(results.coverage == 66.6)
end

describe "update_stats/2" do
Expand Down

0 comments on commit c76d8ed

Please sign in to comment.