cover.mk 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. # Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>
  2. # Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
  3. # This file is part of erlang.mk and subject to the terms of the ISC License.
  4. COVER_REPORT_DIR ?= cover
  5. COVER_DATA_DIR ?= $(COVER_REPORT_DIR)
  6. ifdef COVER
  7. COVER_APPS ?= $(notdir $(ALL_APPS_DIRS))
  8. COVER_DEPS ?=
  9. endif
  10. # Code coverage for Common Test.
  11. ifdef COVER
  12. ifdef CT_RUN
  13. ifneq ($(wildcard $(TEST_DIR)),)
  14. test-build:: $(TEST_DIR)/ct.cover.spec
  15. $(TEST_DIR)/ct.cover.spec: cover-data-dir
  16. $(gen_verbose) printf "%s\n" \
  17. "{incl_app, '$(PROJECT)', details}." \
  18. "{incl_dirs, '$(PROJECT)', [\"$(call core_native_path,$(CURDIR)/ebin)\" \
  19. $(foreach a,$(COVER_APPS),$(comma) \"$(call core_native_path,$(APPS_DIR)/$a/ebin)\") \
  20. $(foreach d,$(COVER_DEPS),$(comma) \"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\")]}." \
  21. '{export,"$(call core_native_path,$(abspath $(COVER_DATA_DIR))/ct.coverdata)"}.' > $@
  22. CT_RUN += -cover $(TEST_DIR)/ct.cover.spec
  23. endif
  24. endif
  25. endif
  26. # Code coverage for other tools.
  27. ifdef COVER
  28. define cover.erl
  29. CoverSetup = fun() ->
  30. Dirs = ["$(call core_native_path,$(CURDIR)/ebin)"
  31. $(foreach a,$(COVER_APPS),$(comma) "$(call core_native_path,$(APPS_DIR)/$a/ebin)")
  32. $(foreach d,$(COVER_DEPS),$(comma) "$(call core_native_path,$(DEPS_DIR)/$d/ebin)")],
  33. [begin
  34. case filelib:is_dir(Dir) of
  35. false -> false;
  36. true ->
  37. case cover:compile_beam_directory(Dir) of
  38. {error, _} -> halt(1);
  39. _ -> true
  40. end
  41. end
  42. end || Dir <- Dirs]
  43. end,
  44. CoverExport = fun(Filename) -> cover:export(Filename) end,
  45. endef
  46. else
  47. define cover.erl
  48. CoverSetup = fun() -> ok end,
  49. CoverExport = fun(_) -> ok end,
  50. endef
  51. endif
  52. # Core targets
  53. ifdef COVER
  54. ifneq ($(COVER_REPORT_DIR),)
  55. tests::
  56. $(verbose) $(MAKE) --no-print-directory cover-report
  57. endif
  58. cover-data-dir: | $(COVER_DATA_DIR)
  59. $(COVER_DATA_DIR):
  60. $(verbose) mkdir -p $(COVER_DATA_DIR)
  61. else
  62. cover-data-dir:
  63. endif
  64. clean:: coverdata-clean
  65. ifneq ($(COVER_REPORT_DIR),)
  66. distclean:: cover-report-clean
  67. endif
  68. help::
  69. $(verbose) printf "%s\n" "" \
  70. "Cover targets:" \
  71. " cover-report Generate a HTML coverage report from previously collected" \
  72. " cover data." \
  73. " all.coverdata Merge all coverdata files into all.coverdata." \
  74. "" \
  75. "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \
  76. "target tests additionally generates a HTML coverage report from the combined" \
  77. "coverdata files from each of these testing tools. HTML reports can be disabled" \
  78. "by setting COVER_REPORT_DIR to empty."
  79. # Plugin specific targets
  80. COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata))
  81. .PHONY: coverdata-clean
  82. coverdata-clean:
  83. $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec
  84. # Merge all coverdata files into one.
  85. define cover_export.erl
  86. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
  87. cover:export("$(COVER_DATA_DIR)/$@"), halt(0).
  88. endef
  89. all.coverdata: $(COVERDATA) cover-data-dir
  90. $(gen_verbose) $(call erlang,$(cover_export.erl))
  91. # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to
  92. # empty if you want the coverdata files but not the HTML report.
  93. ifneq ($(COVER_REPORT_DIR),)
  94. .PHONY: cover-report-clean cover-report
  95. cover-report-clean:
  96. $(gen_verbose) rm -rf $(COVER_REPORT_DIR)
  97. ifneq ($(COVER_REPORT_DIR),$(COVER_DATA_DIR))
  98. $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR))
  99. endif
  100. ifeq ($(COVERDATA),)
  101. cover-report:
  102. else
  103. # Modules which include eunit.hrl always contain one line without coverage
  104. # because eunit defines test/0 which is never called. We compensate for this.
  105. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \
  106. grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \
  107. | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq))
  108. define cover_report.erl
  109. $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),)
  110. Ms = cover:imported_modules(),
  111. [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M)
  112. ++ ".COVER.html", [html]) || M <- Ms],
  113. Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],
  114. EunitHrlMods = [$(EUNIT_HRL_MODS)],
  115. Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of
  116. true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],
  117. TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),
  118. TotalN = lists:sum([N || {_, {_, N}} <- Report1]),
  119. Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end,
  120. TotalPerc = Perc(TotalY, TotalN),
  121. {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]),
  122. io:format(F, "<!DOCTYPE html><html>~n"
  123. "<head><meta charset=\"UTF-8\">~n"
  124. "<title>Coverage report</title></head>~n"
  125. "<body>~n", []),
  126. io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
  127. io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
  128. [io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
  129. "<td>~p%</td></tr>~n",
  130. [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1],
  131. How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))",
  132. Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")",
  133. io:format(F, "</table>~n"
  134. "<p>Generated using ~s and erlang.mk on ~s.</p>~n"
  135. "</body></html>", [How, Date]),
  136. halt().
  137. endef
  138. cover-report:
  139. $(verbose) mkdir -p $(COVER_REPORT_DIR)
  140. $(gen_verbose) $(call erlang,$(cover_report.erl))
  141. endif
  142. endif # ifneq ($(COVER_REPORT_DIR),)