Skip to content
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

Add support to generate XML files #218

Merged
merged 3 commits into from
Mar 17, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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