cover.mk 5.7 KB

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