lib/coverage_ci_poster.ex

defmodule ExIntegrationCoveralls.CoverageCiPoster do
  @moduledoc """
  Handler for posting coverage data to Internal CI Service.
  """
  alias ExIntegrationCoveralls.Cover
  alias ExIntegrationCoveralls.Stats
  alias ExIntegrationCoveralls.Json
  alias ExIntegrationCoveralls.Poster

  @doc """
  Post stats to coverage CI.

  ## Parameters
  - url: CI receive stats address
  - extends_post_params: use to transform stats which CI service can acceptable form
  - compile_time_source_lib_abs_path: source code project abs path in compile-time machine.
  - source_lib_absolute_path: source code project abs path in run-time machine.
  """
  def post_stats_to_cover_ci(
        url,
        extends_post_params,
        compile_time_source_lib_abs_path \\ File.cwd!(),
        source_code_abs_path \\ File.cwd!()
      ) do
    stats =
      get_coverage_stats(compile_time_source_lib_abs_path, source_code_abs_path)
      |> stats_transformer(extends_post_params)

    body = Json.generate_json_output(stats)
    Poster.post_to_coverage_services_center(url, body)
  end

  @doc """
  Get coverage stats.

  ## Return
  A map array.

  ## Examples
      [{"lib/hello.ex", [ nil, 0, nil, 0, nil, 1]}, ...]
  """
  def get_coverage_stats(
        compile_time_source_lib_abs_path,
        source_code_abs_path
      ) do
    stats =
      Cover.modules(source_code_abs_path)
      |> Stats.calculate_stats(compile_time_source_lib_abs_path)
      |> Stats.generate_coverage(source_code_abs_path)

    stats
  end

  @doc """
  Transform stats to CI acceptable form.

  ## Parameters
  - stats: get_coverage_stats() return value
  """
  def stats_transformer(stats, extends \\ %{}) do
    # %{"lib/hello.ex" => %{ 1 => 3, 2 => 0, 3 => -1}}
    files_map =
      Enum.map(stats, fn item ->
        {file_path, line_cov_arr} = item
        file_coverage_map(file_path, line_cov_arr)
      end)
      |> Map.new()

    Map.put_new(extends, :files, files_map)
  end

  # Return value like this: {"lib/hello.ex", %{ 1 => 3, 2 => 0, 3 => -1}}
  defp file_coverage_map(file_path, line_cov_arr) do
    line_cov_map =
      Enum.with_index(line_cov_arr, fn element, index ->
        [index + 1, transform_nil_to_negative_one(element)]
      end)
      |> Map.new(fn [k, v] -> {k, v} end)

    {file_path, line_cov_map}
  end

  defp transform_nil_to_negative_one(value) do
    cond do
      value == nil -> -1
      value -> value
    end
  end
end