Repository: parroty/excoveralls
Pull request #183 (2019-05-07 07:00:59)

Ignore lines between coveralls-ignore-start and coveralls-ignore-stop comments
This pull request addresses issue #177. 

The approach is inspired by the stop-words implementation and ignores all code between `#coveralls-ignore-start` and `#coveralls-ignore-stop` in the coverage calculation

It might not be the most beautiful solution but it helps us a great deal to reach [100% test coverage](https://reflectoring.io/100-percent-test-coverage/).

coveralls: 
[![Coverage Status](https://coveralls.io/builds/23228928/badge)](https://coveralls.io/builds/23228928)

Coverage increased (+0.2%) to 89.63% when pulling **4792329125862a3d7902f89714a33bfed2114a85 on ufirstgroup:support-comments-to-ignore-lines** into **80da82f08539f526acd8aeefc5bfae33c50e5539 on parroty:master**.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/23228928/badge)](https://coveralls.io/builds/23228928)

Coverage increased (+0.2%) to 89.63% when pulling **4792329125862a3d7902f89714a33bfed2114a85 on ufirstgroup:support-comments-to-ignore-lines** into **80da82f08539f526acd8aeefc5bfae33c50e5539 on parroty:master**.


Patch:
diff --git a/README.md b/README.md
index 749a66e..7ca96cb 100644
--- a/README.md
+++ b/README.md
@@ -73,8 +73,9 @@ end
       - [Exclude Files](#exclude-files)
       - [Terminal Report Output](#terminal-report-output)
       - [Coverage Options](#coverage-options)
-    - [Notes](#notes)
-    - [Todo](#todo)
+  - [Ignore Lines](#ignore-lines)
+  - [Notes](#notes)
+  - [Todo](#todo)
 
 ### [mix coveralls] Show coverage
 Run the `MIX_ENV=test mix coveralls` command to show coverage information on localhost.
@@ -349,6 +350,22 @@ If you want to change the column width used for file names add the `file_column_
 }
 ```
 
+### Ignore Lines
+
+Use comments `coveralls-ignore-start` and `coveralls-ignore-stop` to ignore certain lines from code coverage calculation.
+
+```elixir
+defmodule MyModule do
+  def covered do
+  end
+  
+  # coveralls-ignore-start
+  def ignored do
+  end
+  # coveralls-ignore-stop
+end
+```
+
 ### Notes
 - If mock library is used, it will show some warnings during execution.
     - https://github.com/eproxus/meck/pull/17
@@ -359,4 +376,3 @@ If you want to change the column width used for file names add the `file_column_
 ### Todo
 - It might not work well on projects which handle multiple project (Mix.Project) files.
     - Needs improvement on file-path handling.
-
diff --git a/lib/excoveralls/ignore.ex b/lib/excoveralls/ignore.ex
new file mode 100644
index 0000000..24a4c1b
--- /dev/null
+++ b/lib/excoveralls/ignore.ex
@@ -0,0 +1,51 @@
+defmodule ExCoveralls.Ignore do
+  @moduledoc """
+  Handles comments to start/stop ignoring lines from coverage.
+  """
+
+  @doc """
+  Filters out lines between start and end comment.
+  """
+  def filter(info) do
+    Enum.map(info, &do_filter/1)
+  end
+
+  defp do_filter(%{name: name, source: source, coverage: coverage}) do
+    lines = String.split(source, "\n")
+    list = Enum.zip(lines, coverage)
+           |> Enum.map_reduce(false, &check_and_swap/2)
+           |> elem(0)
+           |> List.zip
+           |> Enum.map(&Tuple.to_list(&1))
+
+    [source, coverage] = parse_filter_list(list)
+    %{name: name, source: source, coverage: coverage}
+  end
+
+  defp check_and_swap({line, coverage}, ignore) do
+    {
+      coverage_for_line({line, coverage}, ignore),
+      ignore_next?(line, ignore)
+    }
+  end
+
+  defp parse_filter_list([]),   do: ["", []]
+  defp parse_filter_list([lines, coverage]), do: [Enum.join(lines, "\n"), coverage]
+
+  defp coverage_for_line({line, coverage}, ignore) do
+    if ignore == false do
+      {line, coverage}
+    else
+      {line, nil}
+    end
+  end
+
+  defp ignore_next?(line, ignore) do
+    case Regex.run(~r/coveralls-ignore-(start|stop)/, line, capture: :all_but_first) do
+      ["start"] -> true
+      ["stop"] -> false
+      sth -> ignore
+    end
+  end
+
+end
diff --git a/lib/excoveralls/stats.ex b/lib/excoveralls/stats.ex
index 7af11a1..ccbc1e4 100644
--- a/lib/excoveralls/stats.ex
+++ b/lib/excoveralls/stats.ex
@@ -30,6 +30,7 @@ defmodule ExCoveralls.Stats do
     |> generate_source_info
     |> skip_files
     |> ExCoveralls.StopWords.filter
+    |> ExCoveralls.Ignore.filter
   end
 
   @doc """


Test patch:
diff --git a/test/ignore_test.exs b/test/ignore_test.exs
new file mode 100644
index 0000000..7567255
--- /dev/null
+++ b/test/ignore_test.exs
@@ -0,0 +1,26 @@
+defmodule ExCoveralls.IgnoreTest do
+  use ExUnit.Case
+  alias ExCoveralls.Ignore
+
+  @content     """
+  defmodule Test do
+    def test do
+    end
+    #coveralls-ignore-start
+    def test_ignored do
+    end
+    #coveralls-ignore-stop
+  end
+  """
+  @counts      [0, 0, 0, nil, 0, 0, nil, 0, 0]
+  @source_info [%{name: "test/fixtures/test.ex",
+                 source: @content,
+                 coverage: @counts
+               }]
+
+  test "filter ignored lines returns valid list" do
+    info = Ignore.filter(@source_info) |> Enum.at(0)
+    assert(info[:source]   == @content)
+    assert(info[:coverage] == [0, 0, 0, nil, nil, nil, nil, 0, 0])
+  end
+end