diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..062e99fa --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,21 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug", + "program": "${workspaceFolder}/build/src/kcov", + "args": [ + "./cov", + "--include-path=/Users/bytedance/DevCamp/jstring", + "--clean", + "/Users/bytedance/DevCamp/jstring/zig-out/bin/pcre_test" + ], + "cwd": "${workspaceFolder}/../jstring" + } + ] +} diff --git a/README.md b/README.md index 6e5e2d05..6075ca10 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,51 @@ +# A fork of `kcov` for better covering Zig + +This is a rather than naive fork of wonderful `kcov` for better supporting Zig projects. + +## It adds the ability to auto recognize Zig's `unreachable` and `@panic` for auto ignoring. + +Here is a screenshot of auto ignoring `unreachable`. + +![zig-unreachable-example](https://github.com/liyu1981/kcov/blob/master/nocover/zig-unreachable.png?raw=true) + +Here is another screenshot of auto ignoring `@panic`. + +![zig-panic-example](https://github.com/liyu1981/kcov/blob/master/nocover/zig-panic.png?raw=true) + +## It also adds the ability in c/c++ source file of using `/* no-cover */` to mark a line to be ignored. + +Here is a screenshot of manual ignoring by placing `/* no-cover */` in c source file. + +![c-no-cover example](https://github.com/liyu1981/kcov/blob/master/nocover/c-nocover.png?raw=true) + +## Usage + +There is no change of original `kcov` usage, it will just work. Please follow the below original Kcov README. + +### But how can I get the binary? + +The best way is to compile from source. It can be done as follows (you will need `cmake`, `ninja`, `llvm@>16` as I tried) + +``` +git clone https://github.com/liyu1981/kcov.git +cd kcov +mkdir build +cd build +CC="clang" CXX="clang++" cmake -G Ninja .. +ninja +``` + +after building is done. The binary is at `build/src/kcov`. Copy somewhere and use it. + +(Or you can download a copy of Apple Silicon version binary from the release section.) + +## File Support + +- for Zig, support source file with `.zig` extension. +- for C/C++, support source file with `.c/.cpp/.cc` extension. + +## Original Kcov Readme + [![Coveralls coverage status](https://img.shields.io/coveralls/SimonKagstrom/kcov.svg)](https://coveralls.io/r/SimonKagstrom/kcov?branch=master) [![Codecov coverage status](https://codecov.io/gh/SimonKagstrom/kcov/branch/master/graph/badge.svg)](https://codecov.io/gh/SimonKagstrom/kcov) [![Coverity Scan Build Status](https://scan.coverity.com/projects/2844/badge.svg)](https://scan.coverity.com/projects/2844) @@ -5,9 +53,10 @@ [![PayPal Donate](https://img.shields.io/badge/paypal-donate-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=simon.kagstrom%40gmail%2ecom&lc=US&item_name=Simon%20Kagstrom&item_number=kcov¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHosted) [![Github All Releases](https://img.shields.io/github/downloads/atom/atom/total.svg)](https://github.com/SimonKagstrom/kcov/) -## *kcov* +## _kcov_ + Kcov is a FreeBSD/Linux/OSX code coverage tester for compiled languages, Python -and Bash. Kcov was originally a fork of [Bcov](http://bcov.sf.net), but has +and Bash. Kcov was originally a fork of [Bcov](http://bcov.sf.net), but has since evolved to support a large feature set in addition to that of Bcov. Kcov, like Bcov, uses DWARF debugging information for compiled programs to @@ -16,28 +65,27 @@ switches. For a video introduction, [look at this presentation from SwedenCPP](https://www.youtube.com/watch?v=1QMHbp5LUKg) -Installing ----------- +## Installing + Refer to the [INSTALL](INSTALL.md) file for build instructions, or use our official Docker images: -* [kcov/kcov](https://hub.docker.com/r/kcov/kcov/) for releases since v31. +- [kcov/kcov](https://hub.docker.com/r/kcov/kcov/) for releases since v31. +## How to use it -How to use it -------------- Basic usage is straight-forward: ``` kcov /path/to/outdir executable [args for the executable] ``` -*/path/to/outdir* will contain lcov-style HTML output generated +_/path/to/outdir_ will contain lcov-style HTML output generated continuously while the application runs. Kcov will also write cobertura- compatible XML output and generic JSON coverage information and can easily be integrated in various CI systems. -Filtering output ----------------- +## Filtering output + It's often useful to filter output, since e.g., /usr/include is seldom of interest. This can be done in two ways: @@ -47,8 +95,8 @@ kcov --exclude-pattern=/usr/include --include-pattern=part/of/path,other/path \ ``` which will do a string-comparison and include everything which contains -*part/of/path* or *other/path* but exclude everything that has the -*/usr/include* string in it. +_part/of/path_ or _other/path_ but exclude everything that has the +_/usr/include_ string in it. ``` kcov --include-path=/my/src/path /path/to/outdir executable @@ -57,8 +105,8 @@ kcov --exclude-path=/usr/include /path/to/outdir executable Does the same thing, but with proper path lookups. -Merging multiple kcov runs --------------------------- +## Merging multiple kcov runs + Kcov can also merge the results of multiple earlier runs. To use this mode, call kcov with `--merge`, an output path and one or more paths to an earlier run, e.g., @@ -68,20 +116,20 @@ kcov --merge /tmp/merged-output /tmp/kcov-output1 /tmp/kcov-output2 kcov --merge /tmp/merged-output /tmp/kcov-output* # With a wildcard ``` -Use from continuous integration systems ---------------------------------------- +## Use from continuous integration systems + kcov is easy to integrate with [travis-ci](http://travis-ci.org) together with [coveralls.io](http://coveralls.io) or [codecov.io](http://codecov.io). It can also be used from Jenkins, [SonarQube](http://sonarqube.org) and [GitLab CI](http://gitlab.com). Refer to -* [coveralls](doc/coveralls.md) for details about travis-ci + coveralls, or -* [codecov](doc/codecov.md) for details about travis-ci + codecov -* [jenkins](doc/jenkins.md) for details about how to integrate in Jenkins -* [sonarqube](doc/sonarqube.md) for how to use kcov and sonarqube together -* [gitlab](doc/gitlab.md) for use with GitLab +- [coveralls](doc/coveralls.md) for details about travis-ci + coveralls, or +- [codecov](doc/codecov.md) for details about travis-ci + codecov +- [jenkins](doc/jenkins.md) for details about how to integrate in Jenkins +- [sonarqube](doc/sonarqube.md) for how to use kcov and sonarqube together +- [gitlab](doc/gitlab.md) for use with GitLab + +## More information -More information ----------------- kcov is written by Simon Kagstrom and more information can be found at [the web page](http://simonkagstrom.github.io/kcov/index.html) diff --git a/nocover/c-nocover.png b/nocover/c-nocover.png new file mode 100644 index 00000000..cc6d1b3b Binary files /dev/null and b/nocover/c-nocover.png differ diff --git a/nocover/zig-panic.png b/nocover/zig-panic.png new file mode 100644 index 00000000..13ec000e Binary files /dev/null and b/nocover/zig-panic.png differ diff --git a/nocover/zig-unreachable.png b/nocover/zig-unreachable.png new file mode 100644 index 00000000..e04bae82 Binary files /dev/null and b/nocover/zig-unreachable.png differ diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 3ab7c200..633b13ed 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -239,6 +239,7 @@ set (${KCOV}_SRCS writers/html-writer.cc writers/sonarqube-xml-writer.cc writers/writer-base.cc + writers/nocover.cc ${ELF_SRCS} ${MACHO_SRCS} include/capabilities.hh diff --git a/src/writers/codecov-writer.cc b/src/writers/codecov-writer.cc index a57ccded..a2e99da8 100644 --- a/src/writers/codecov-writer.cc +++ b/src/writers/codecov-writer.cc @@ -18,6 +18,7 @@ class type_info; #include #include "writer-base.hh" +#include "nocover.hh" namespace kcov { @@ -134,6 +135,12 @@ class CodecovWriter : public WriterBase IReporter::LineExecutionCount cnt = m_reporter.getLineExecutionCount(file->m_name, n); std::string hitScore = "0"; + std::string& line_str = file->m_lineMap.at(n); + std::string& file_name = file->m_fileName; + if (!shouldCover(line_str, file_name)) { + continue; + } + if (m_maxPossibleHits == IFileParser::HITS_UNLIMITED || m_maxPossibleHits == IFileParser::HITS_SINGLE) { if (cnt.m_hits) { @@ -167,7 +174,7 @@ class CodecovWriter : public WriterBase linesBlock += "\n"; - std::string out = + std::string out = " \"" + filename + "\": {\n" + linesBlock + " }"; diff --git a/src/writers/html-writer.cc b/src/writers/html-writer.cc index 0bae8525..3d31e340 100644 --- a/src/writers/html-writer.cc +++ b/src/writers/html-writer.cc @@ -16,6 +16,7 @@ #include #include "writer-base.hh" +#include "nocover.hh" using namespace kcov; @@ -72,6 +73,11 @@ class HtmlWriter : public WriterBase "\"line\":\"", n); outJson << escape_json(line) << "\""; + std::string& line_str = file->m_lineMap[n]; + std::string& file_name = file->m_name; + bool should_cover = shouldCover(line_str, file_name); + const std::string& no_cover_class = "lineNoCov"; + if (m_reporter.lineIsCode(file->m_name, n)) { IReporter::LineExecutionCount cnt = m_reporter.getLineExecutionCount(file->m_name, n); @@ -90,18 +96,21 @@ class HtmlWriter : public WriterBase lineClass = "linePartCov"; } - outJson << fmt(",\"class\":\"%s\"," - "\"hits\":\"%u\",", lineClass.c_str(), cnt.m_hits); - - // Don't report order for zeroes - if (cnt.m_order) - outJson << fmt("\"order\":\"%llu\",", (unsigned long long) cnt.m_order); + if (lineClass == no_cover_class && !should_cover) { + // should not cover, so skip, but if there are some hits (when lineClass is not no cov), go else for normal marking + } else { + outJson << fmt(",\"class\":\"%s\"," + "\"hits\":\"%u\",", lineClass.c_str(), cnt.m_hits); + // Don't report order for zeroes + if (cnt.m_order) + outJson << fmt("\"order\":\"%llu\",", (unsigned long long) cnt.m_order); - if (m_maxPossibleHits != IFileParser::HITS_SINGLE) - outJson << fmt("\"possible_hits\":\"%u\",", cnt.m_possibleHits); + if (m_maxPossibleHits != IFileParser::HITS_SINGLE) + outJson << fmt("\"possible_hits\":\"%u\",", cnt.m_possibleHits); - nExecutedLines += !!cnt.m_hits; - nCodeLines++; + nExecutedLines += !!cnt.m_hits; + nCodeLines++; + } } outJson << "},\n"; @@ -252,15 +261,29 @@ class HtmlWriter : public WriterBase if (!res) continue; + // since we may ignored some from previous individual writeOne functions for each file, here use the sum from + // files instead of summary + int inFilesTotalCodeLines = 0; + int inFilesTotalExecutedLines = 0; + for (FileMap_t::const_iterator it = m_files.begin(); it != m_files.end(); ++it) + { + inFilesTotalCodeLines += it->second->m_codeLines; + inFilesTotalExecutedLines += it->second->m_executedLines; + } + // Skip entries (merged ones) that shouldn't be included in the totals if (summary.m_includeInTotals) { - nTotalCodeLines += summary.m_lines; - nTotalExecutedLines += summary.m_executedLines; + nTotalCodeLines += inFilesTotalCodeLines; + nTotalExecutedLines += inFilesTotalExecutedLines; + // nTotalCodeLines += summary.m_lines; + // nTotalExecutedLines += summary.m_executedLines; } - std::string datum = getIndexHeader(fmt("%s/index.html", de->d_name), name, name, summary.m_lines, - summary.m_executedLines); + std::string datum = getIndexHeader(fmt("%s/index.html", de->d_name), name, name, inFilesTotalCodeLines, + inFilesTotalExecutedLines); + // std::string datum = getIndexHeader(fmt("%s/index.html", de->d_name), name, name, summary.m_lines, + // summary.m_executedLines); if (name == conf.keyAsString("merged-name")) merged += datum; diff --git a/src/writers/json-writer.cc b/src/writers/json-writer.cc index d0d124d4..f875566d 100644 --- a/src/writers/json-writer.cc +++ b/src/writers/json-writer.cc @@ -16,6 +16,7 @@ class type_info; #include #include "writer-base.hh" +#include "nocover.hh" using namespace kcov; @@ -61,12 +62,21 @@ class JsonWriter : public WriterBase for (unsigned int n = 1; n < file->m_lastLineNr; n++) { IReporter::LineExecutionCount cnt = m_reporter.getLineExecutionCount(file->m_name, n); + + const std::string& line_str = file->m_lineMap[n]; + const std::string& file_name = it->first; + bool should_cover = shouldCover(line_str, file_name); + if (m_reporter.lineIsCode(file->m_name, n)) { - nExecutedLines += !!cnt.m_hits; - nCodeLines++; - nTotalExecutedLines += !!cnt.m_hits; - nTotalCodeLines++; + if (should_cover) { + nExecutedLines += !!cnt.m_hits; + nCodeLines++; + nTotalExecutedLines += !!cnt.m_hits; + nTotalCodeLines++; + } else { + // skip this from counting + } } } if (nCodeLines > 0) diff --git a/src/writers/nocover.cc b/src/writers/nocover.cc new file mode 100644 index 00000000..e0b6915b --- /dev/null +++ b/src/writers/nocover.cc @@ -0,0 +1,84 @@ +#include "nocover.hh" + +#include +#include + +namespace kcov +{ + +enum FileType { + zig = 0, + c = 1, + cpp = 2, +}; + +bool endsWith(const std::string& full_string, const std::string& ending) { + if (full_string.length() >= ending.length()) { + return (full_string.compare(full_string.length() - ending.length(), ending.length(), ending) == 0); + } else { + return false; + } +} + +FileType getFileExt(const std::string& file_name) { + const std::string zig_ending = ".zig"; + const std::string c_ending = ".c"; + const std::string cpp_ending1 = ".cpp"; + const std::string cpp_ending2 = ".cc"; + + if (endsWith(file_name, zig_ending)) { + return zig; + } + + if (endsWith(file_name, c_ending)) { + return c; + } + + if (endsWith(file_name, cpp_ending1) || endsWith(file_name, cpp_ending2)) { + return cpp; + } + + std::cerr << "Not support file name:" << file_name << std::endl; + + std::abort(); +} + +bool shouldCover(const std::string& line, const std::string& file_name) { + FileType t = getFileExt(file_name); + + switch(t) { + case zig: { + const std::string zig_nocover_1 = "unreachable"; + const std::string zig_nocover_2 = "@panic"; + + if (line.find(zig_nocover_1) != std::string::npos) { + return false; + } + if (line.find(zig_nocover_2) != std::string::npos) { + return false; + } + return true; + } + break; + + case c: { + const std::string c_nocover = "/* no-cover */"; + if (line.find(c_nocover) != std::string::npos) { + return false; + } + return true; + } + break; + + case cpp: { + const std::string cpp_nocover = "/* no-cover */"; + if (line.find(cpp_nocover) != std::string::npos) { + return false; + } + return true; + } + break; + } +} + +} diff --git a/src/writers/nocover.hh b/src/writers/nocover.hh new file mode 100644 index 00000000..b151f460 --- /dev/null +++ b/src/writers/nocover.hh @@ -0,0 +1,12 @@ +#ifndef _NOCOVER_H_ +#define _NOCOVER_H_ + +#include +namespace kcov +{ + +bool shouldCover(const std::string& line, const std::string& file_name); + +} + +#endif