Skip to content

Commit

Permalink
Merge pull request #218 from adrienmo/xml
Browse files Browse the repository at this point in the history
Add support to generate XML files
  • Loading branch information
parroty committed Mar 17, 2020
2 parents 0a1aa01 + 22cde52 commit 506dd8f
Show file tree
Hide file tree
Showing 6 changed files with 162 additions and 1 deletion.
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ end
- [[mix coveralls.detail] Show coverage with detail](#mix-coverallsdetail-show-coverage-with-detail)
- [[mix coveralls.html] Show coverage as HTML report](#mix-coverallshtml-show-coverage-as-html-report)
- [[mix coveralls.json] Show coverage as JSON report](#mix-coverallsjson-show-coverage-as-json-report)
- [[mix coveralls.xml] Show coverage as XML report](#mix-coverallsjson-show-coverage-as-xml-report)
- [coveralls.json](#coverallsjson)
- [Stop Words](#stop-words)
- [Exclude Files](#exclude-files)
Expand Down Expand Up @@ -321,6 +322,13 @@ or to Code Climate using their [test-reporter](https://docs.codeclimate.com/docs

Output reports are written to `cover/excoveralls.json` by default, however, the path can be specified by overwriting the `"output_dir"` coverage option.

### [mix coveralls.xml] Show coverage as XML report
This task displays coverage information at the source-code level formatted as a XML document.
The report follows a format supported by several code coverage services like SonarQube.
Output to the shell is the same as running the command `mix coveralls` (to suppress this output, add `"print_summary": false` to your project's `coveralls.json` file). In a similar manner to `mix coveralls.detail`, reported source code can be filtered by specifying arguments using the `--filter` flag.

Output reports are written to `cover/excoveralls.xml` by default, however, the path can be specified by overwriting the `"output_dir"` coverage option.

## coveralls.json
`coveralls.json` provides settings for excoveralls.

Expand Down Expand Up @@ -394,7 +402,8 @@ to `false`:
"treat_no_relevant_lines_as_covered": true,
"output_dir": "cover/",
"template_path": "custom/path/to/template/",
"minimum_coverage": 90
"minimum_coverage": 90,
"xml_base_dir": "custom/path/for/xml/reports/"
}
}
```
Expand Down
9 changes: 9 additions & 0 deletions lib/excoveralls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ defmodule ExCoveralls do
alias ExCoveralls.Html
alias ExCoveralls.Json
alias ExCoveralls.Post
alias ExCoveralls.Xml

@type_travis "travis"
@type_github "github"
Expand All @@ -26,6 +27,7 @@ defmodule ExCoveralls do
@type_html "html"
@type_json "json"
@type_post "post"
@type_xml "xml"

@doc """
This method will be called from mix to trigger coverage analysis.
Expand Down Expand Up @@ -110,6 +112,13 @@ defmodule ExCoveralls do
Json.execute(stats, options)
end

@doc """
Logic for XML output, without posting server
"""
def analyze(stats, @type_xml, options) do
Xml.execute(stats, options)
end

@doc """
Logic for posting from general CI server with token.
"""
Expand Down
7 changes: 7 additions & 0 deletions lib/excoveralls/settings.ex
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,13 @@ defmodule ExCoveralls.Settings do
end
end

@doc """
Get xml base dir
"""
def get_xml_base_dir do
Map.get(get_coverage_options(), "xml_base_dir", "")
end

@doc """
Get skip files from the json file.
"""
Expand Down
56 changes: 56 additions & 0 deletions lib/excoveralls/xml.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
defmodule ExCoveralls.Xml do
@moduledoc """
Generate XML output for results.
"""

alias ExCoveralls.Settings

@file_name "excoveralls.xml"

@doc """
Provides an entry point for the module.
"""
def execute(stats, options \\ []) do
stats
|> generate_xml(Enum.into(options, %{}))
|> write_file(options[:output_dir])

ExCoveralls.Local.print_summary(stats)
end

def generate_xml(stats, _options) do
base_dir = Settings.get_xml_base_dir()
"<coverage version=\"1\">" <> Enum.map_join(stats, fn %{name: name, coverage: coverage} ->
path = String.replace("#{base_dir}/#{name}", ~r/(\/)+/, "/", global: true)
"<file path=\"#{path}\">" <>
Enum.map_join(Enum.with_index(coverage), fn
{nil, _line} -> ""
{count, line} ->
"<lineToCover lineNumber=\"#{line + 1}\" covered=\"#{count != 0}\"/>"
end)
<> "</file>"
end) <> "</coverage>"
end

defp output_dir(output_dir) do
cond do
output_dir ->
output_dir
true ->
options = Settings.get_coverage_options
case Map.fetch(options, "output_dir") do
{:ok, val} -> val
_ -> "cover/"
end
end
end

defp write_file(content, output_dir) do
file_path = output_dir(output_dir)
unless File.exists?(file_path) do
File.mkdir_p!(file_path)
end
File.write!(Path.expand(@file_name, file_path), content)
end

end
15 changes: 15 additions & 0 deletions lib/mix/tasks.ex
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,21 @@ defmodule Mix.Tasks.Coveralls do
end
end

defmodule Xml do
@moduledoc """
Provides an entry point for outputting coveralls information
as a XML file.
"""
use Mix.Task

@shortdoc "Output the test coverage as a XML file"
@preferred_cli_env :test

def run(args) do
Mix.Tasks.Coveralls.do_run(args, [ type: "xml" ])
end
end

defmodule Json do
@moduledoc """
Provides an entry point for outputting coveralls information
Expand Down
65 changes: 65 additions & 0 deletions test/xml_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
defmodule ExCoveralls.XmlTest do
use ExUnit.Case
import Mock
import ExUnit.CaptureIO
alias ExCoveralls.Xml

@file_name "excoveralls.xml"
@test_output_dir "cover_test/"

@content "defmodule Test do\n def test do\n end\nend\n"
@counts [0, 1, nil, nil]
@source_info [%{name: "test/fixtures/test.ex",
source: @content,
coverage: @counts
}]

@stats_result "" <>
"----------------\n" <>
"COV FILE LINES RELEVANT MISSED\n" <>
" 50.0% test/fixtures/test.ex 4 2 1\n" <>
"[TOTAL] 50.0%\n" <>
"----------------\n"

setup do
path = Path.expand(@file_name, @test_output_dir)

# Assert does not exist prior to write
assert(File.exists?(path) == false)
on_exit fn ->
if File.exists?(path) do
# Ensure removed after test
File.rm!(path)
File.rmdir!(@test_output_dir)
end
end

{:ok, report: path}
end

test_with_mock "generate xml file", %{report: report}, ExCoveralls.Settings, [],
[
get_coverage_options: fn -> %{"output_dir" => @test_output_dir} end,
get_file_col_width: fn -> 40 end,
get_print_summary: fn -> true end,
get_print_files: fn -> true end,
get_xml_base_dir: fn -> "base_dir" end
] do

assert capture_io(fn ->
Xml.execute(@source_info)
end) =~ @stats_result

assert File.read!(report) =~ ~s(<coverage version="1"><file path="base_dir/test/fixtures/test.ex"><lineToCover lineNumber="1" covered="false"/><lineToCover lineNumber="2" covered="true"/></file></coverage>)
assert %{size: 173} = File.stat! report
end

test "generate json file with output_dir parameter", %{report: report} do
assert capture_io(fn ->
Xml.execute(@source_info, [output_dir: @test_output_dir])
end) =~ @stats_result

assert File.read!(report) =~ ~s(<coverage version="1"><file path="/test/fixtures/test.ex"><lineToCover lineNumber="1" covered="false"/><lineToCover lineNumber="2" covered="true"/></file></coverage>)
assert %{size: 165} = File.stat! report
end
end

0 comments on commit 506dd8f

Please sign in to comment.