wsd: support code-coverage report via --with-coverage
This adds support for code-coverage HTML reporting. To achieve this, we must use file-linking in jails so that we can update the coverage data (.gcda files) from the jails. This means that creating jails is slower than with bind-mounting and we need to account for that in our timeouts. We also can't kill child processes with SIGKILL, which is un-catchable. Instead, we use SIGTERM and dump the profile data before exiting. Change-Id: I16fa534f6ed42f7133014d841bb024423315e0a4 Signed-off-by: Ashod Nakashian <ashod.nakashian@collabora.co.uk>
This commit is contained in:
parent
551454c9f5
commit
5c6516e4e4
12 changed files with 213 additions and 6 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -64,6 +64,11 @@ coolwsd.log
|
|||
*.lo
|
||||
*.mo
|
||||
|
||||
gcov/*
|
||||
*.gcno
|
||||
*.gcda
|
||||
*.gcov
|
||||
|
||||
# browser
|
||||
browser/debug
|
||||
browser/release
|
||||
|
|
22
Makefile.am
22
Makefile.am
|
@ -327,6 +327,9 @@ noinst_HEADERS = $(wsd_headers) $(shared_headers) $(kit_headers) \
|
|||
test/WOPIUploadConflictCommon.hpp \
|
||||
test/helpers.hpp
|
||||
|
||||
GIT_BRANCH := $(shell git symbolic-ref --short HEAD)
|
||||
GIT_HASH := $(shell git log -1 --format=%h)
|
||||
|
||||
dist-hook:
|
||||
git log -1 --format=%h > $(distdir)/dist_git_hash 2> /dev/null || rm $(distdir)/dist_git_hash
|
||||
mkdir -p $(distdir)/bundled/include/LibreOfficeKit/
|
||||
|
@ -418,6 +421,8 @@ clean-local:
|
|||
rm -rf "${top_srcdir}/loleaflet"
|
||||
rm -rf loolconfig loolconvert loolforkit loolmap loolmount # kill old binaries
|
||||
rm -rf loolwsd loolwsd_fuzzer coolwsd_fuzzer loolstress loolsocketdump
|
||||
rm -rf ${abs_top_srcdir}/gcov
|
||||
find . -iname "*.gc??" -delete
|
||||
|
||||
if ENABLE_DEBUG
|
||||
# can write to /tmp/coolwsd.log
|
||||
|
@ -612,5 +617,22 @@ stress:
|
|||
$(stress_file) $(trace_dir)/writer-hello-shape.txt \
|
||||
$(stress_file) $(trace_dir)/writer-quick.txt
|
||||
|
||||
if ENABLE_CODE_COVERAGE
|
||||
GEN_COVERAGE_COMMAND=mkdir -p ${abs_top_srcdir}/gcov && \
|
||||
lcov --no-external --capture --rc 'lcov_excl_line=' --rc 'lcov_excl_br_line=LOG_|TST_|LOK_|WSD_|TRANSITION|assert' \
|
||||
--compat libtool=on --directory ${abs_top_srcdir}/. --output-file ${abs_top_srcdir}/gcov/cool.coverage.test.info && \
|
||||
genhtml --prefix ${abs_top_srcdir}/. --ignore-errors source ${abs_top_srcdir}/gcov/cool.coverage.test.info \
|
||||
--legend --title "${GIT_BRANCH} @ ${GIT_HASH}" --output-directory=${abs_top_srcdir}/gcov/html && \
|
||||
echo "Code-Coverage report generated in ${abs_top_srcdir}/gcov/html"
|
||||
else
|
||||
GEN_COVERAGE_COMMAND=true
|
||||
endif
|
||||
|
||||
check: check-recursive
|
||||
$(GEN_COVERAGE_COMMAND)
|
||||
|
||||
coverage-report:
|
||||
$(GEN_COVERAGE_COMMAND)
|
||||
|
||||
czech: check
|
||||
@echo "This should do something much cooler"
|
||||
|
|
|
@ -22,6 +22,8 @@ constexpr int DEFAULT_CLIENT_PORT_NUMBER = 9980;
|
|||
|
||||
#if VALGRIND_COOLFORKIT
|
||||
constexpr int TRACE_MULTIPLIER = 20;
|
||||
#elif CODE_COVERAGE
|
||||
constexpr int TRACE_MULTIPLIER = 5;
|
||||
#else
|
||||
constexpr int TRACE_MULTIPLIER = 1;
|
||||
#endif
|
||||
|
|
|
@ -317,6 +317,10 @@ namespace SigUtil
|
|||
SocketPoll::wakeupWorld();
|
||||
else
|
||||
{
|
||||
#if CODE_COVERAGE
|
||||
__gcov_dump();
|
||||
#endif
|
||||
|
||||
::signal (signal, SIG_DFL);
|
||||
::raise (signal);
|
||||
}
|
||||
|
|
|
@ -1095,6 +1095,11 @@ namespace Util
|
|||
{
|
||||
LOG_FTL("Forced Exit with code: " << code);
|
||||
Log::shutdown();
|
||||
|
||||
#if CODE_COVERAGE
|
||||
__gcov_dump();
|
||||
#endif
|
||||
|
||||
std::_Exit(code);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,6 +42,15 @@
|
|||
|
||||
#include <StringVector.hpp>
|
||||
|
||||
#if CODE_COVERAGE
|
||||
extern "C"
|
||||
{
|
||||
void __gcov_reset(void);
|
||||
void __gcov_flush(void);
|
||||
void __gcov_dump(void);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Format seconds with the units suffix until we migrate to C++20.
|
||||
inline std::ostream& operator<<(std::ostream& os, const std::chrono::seconds& s)
|
||||
{
|
||||
|
|
23
configure.ac
23
configure.ac
|
@ -299,6 +299,11 @@ AC_ARG_WITH([infobar-url],
|
|||
AS_HELP_STRING([--with-infobar-url=<url>],
|
||||
[Infobar URL.]))
|
||||
|
||||
AC_ARG_WITH([coverage],
|
||||
AS_HELP_STRING([--with-coverage=<gcov>],
|
||||
[Enable a code-coverage method. Currently only gcov is supported (works with both gcc and clang).
|
||||
Output HTML in gcov/ directory.]))
|
||||
|
||||
AC_ARG_WITH([sanitizer],
|
||||
AS_HELP_STRING([--with-sanitizer],
|
||||
[Enable one or more compatible sanitizers. E.g. --with-sanitizer=address,undefined,leak]))
|
||||
|
@ -843,6 +848,23 @@ else
|
|||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
|
||||
# Code Coverage.
|
||||
AC_MSG_CHECKING([whether code-coverage is enabled])
|
||||
if test "x$with_coverage" != "x"; then
|
||||
AC_MSG_RESULT([yes ($with_coverage)])
|
||||
CODE_COVERAGE=1
|
||||
CXXFLAGS="$CXXFLAGS --coverage"
|
||||
CFLAGS="$CFLAGS --coverage"
|
||||
LDFLAGS="$LDFLAGS -Wl,--dynamic-list-data --coverage"
|
||||
coverage_msg="Code-Coverage is enabled"
|
||||
else
|
||||
AC_MSG_RESULT([no])
|
||||
CODE_COVERAGE=0
|
||||
coverage_msg="Code-Coverage is disabled"
|
||||
fi
|
||||
AC_DEFINE_UNQUOTED([CODE_COVERAGE],[$CODE_COVERAGE],[Define to 1 if this is a code-coverage build.])
|
||||
AM_CONDITIONAL([ENABLE_CODE_COVERAGE], [test "$CODE_COVERAGE" = "1"])
|
||||
|
||||
# Fuzzing.
|
||||
AC_MSG_CHECKING([whether to build fuzzers])
|
||||
if test "$enable_fuzzers" = "yes"; then
|
||||
|
@ -1585,6 +1607,7 @@ Configuration:
|
|||
Browsersync $browsersync_msg
|
||||
cypress $cypress_msg
|
||||
C++ compiler flags $CXXFLAGS
|
||||
Code-Coverage $coverage_msg
|
||||
|
||||
\$ make # to compile"
|
||||
if test -n "$with_lo_path"; then
|
||||
|
|
|
@ -748,7 +748,7 @@ int main(int argc, char** argv)
|
|||
const int parentPid = getppid();
|
||||
LOG_INF("ForKit process is ready. Parent: " << parentPid);
|
||||
|
||||
while (!SigUtil::getTerminationFlag())
|
||||
while (!SigUtil::getShutdownRequestFlag() && !SigUtil::getTerminationFlag())
|
||||
{
|
||||
UnitKit::get().invokeForKitTest();
|
||||
|
||||
|
|
109
kit/Kit.cpp
109
kit/Kit.cpp
|
@ -499,6 +499,104 @@ namespace
|
|||
}
|
||||
}
|
||||
|
||||
#if CODE_COVERAGE
|
||||
std::string childRootForGCDAFiles;
|
||||
std::string sourceForGCDAFiles;
|
||||
std::string destForGCDAFiles;
|
||||
|
||||
int linkGCDAFilesFunction(const char* fpath, const struct stat*, int typeflag,
|
||||
struct FTW* /*ftwbuf*/)
|
||||
{
|
||||
if (strcmp(fpath, sourceForGCDAFiles.c_str()) == 0)
|
||||
{
|
||||
LOG_TRC("nftw: Skipping redundant path: " << fpath);
|
||||
return FTW_CONTINUE;
|
||||
}
|
||||
|
||||
if (Util::startsWith(fpath, childRootForGCDAFiles))
|
||||
{
|
||||
LOG_TRC("nftw: Skipping childRoot subtree: " << fpath);
|
||||
return FTW_SKIP_SUBTREE;
|
||||
}
|
||||
|
||||
assert(fpath[strlen(sourceForGCDAFiles.c_str())] == '/');
|
||||
const char* relativeOldPath = fpath + strlen(sourceForGCDAFiles.c_str()) + 1;
|
||||
const Poco::Path newPath(destForGCDAFiles, Poco::Path(relativeOldPath));
|
||||
|
||||
switch (typeflag)
|
||||
{
|
||||
case FTW_F:
|
||||
case FTW_SLN:
|
||||
{
|
||||
const char* dot = strrchr(relativeOldPath, '.');
|
||||
if (dot && !strcmp(dot, ".gcda"))
|
||||
{
|
||||
Poco::File(newPath.parent()).createDirectories();
|
||||
if (link(fpath, newPath.toString().c_str()) != 0)
|
||||
{
|
||||
LOG_SYS("nftw: Failed to link [" << fpath << "] -> [" << newPath.toString()
|
||||
<< ']');
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case FTW_D:
|
||||
case FTW_SL:
|
||||
break;
|
||||
case FTW_DNR:
|
||||
LOG_ERR("nftw: Cannot read directory '" << fpath << '\'');
|
||||
break;
|
||||
case FTW_NS:
|
||||
LOG_ERR("nftw: stat failed for '" << fpath << '\'');
|
||||
break;
|
||||
default:
|
||||
LOG_FTL("nftw: unexpected typeflag: '" << typeflag);
|
||||
assert(!"nftw: unexpected typeflag.");
|
||||
break;
|
||||
}
|
||||
|
||||
return FTW_CONTINUE;
|
||||
}
|
||||
|
||||
/// Link .gcda (gcov) files from the src directory into the jail.
|
||||
/// We need this so we can easily extract the profile data from within
|
||||
/// the jail. Otherwise, we lose coverage info of the kit process.
|
||||
void linkGCDAFiles(const std::string& destPath)
|
||||
{
|
||||
Poco::Path sourcePathInJail(destPath);
|
||||
const auto sourcePath = std::string(DEBUG_ABSSRCDIR);
|
||||
sourcePathInJail.append(sourcePath);
|
||||
Poco::File(sourcePathInJail).createDirectories();
|
||||
LOG_INF("Linking .gcda files from " << sourcePath << " -> " << sourcePathInJail.toString());
|
||||
|
||||
const auto childRootPtr = std::getenv("BASE_CHILD_ROOT");
|
||||
if (childRootPtr == nullptr || strlen(childRootPtr) == 0)
|
||||
{
|
||||
LOG_ERR("Cannot collect code-coverage stats for the Kit processes. BASE_CHILD_ROOT "
|
||||
"envar missing.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Trim the trailing /.
|
||||
const std::string childRoot = childRootPtr;
|
||||
const size_t last = childRoot.find_last_not_of('/');
|
||||
if (last != std::string::npos)
|
||||
childRootForGCDAFiles = childRoot.substr(0, last + 1);
|
||||
else
|
||||
childRootForGCDAFiles = childRoot;
|
||||
|
||||
sourceForGCDAFiles = sourcePath;
|
||||
destForGCDAFiles = sourcePathInJail.toString() + '/';
|
||||
LOG_INF("nftw .gcda files from " << sourceForGCDAFiles << " -> " << destForGCDAFiles << " ("
|
||||
<< childRootForGCDAFiles << ')');
|
||||
|
||||
if (nftw(sourcePath.c_str(), linkGCDAFilesFunction, 10, FTW_ACTIONRETVAL | FTW_PHYS) == -1)
|
||||
{
|
||||
LOG_ERR("linkGCDAFiles: nftw() failed for '" << sourcePath << '\'');
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef __FreeBSD__
|
||||
void dropCapability(cap_value_t capability)
|
||||
{
|
||||
|
@ -2595,6 +2693,12 @@ void lokit_main(
|
|||
bool bindMount = JailUtil::isBindMountingEnabled();
|
||||
if (bindMount)
|
||||
{
|
||||
#if CODE_COVERAGE
|
||||
// Code coverage is not supported with bind-mounting.
|
||||
LOG_ERR("Mounting is not compatible with code-coverage.");
|
||||
assert(!"Mounting is not compatible with code-coverage.");
|
||||
#endif // CODE_COVERAGE
|
||||
|
||||
if (!mountJail())
|
||||
{
|
||||
LOG_INF("Cleaning up jail before linking/copying.");
|
||||
|
@ -2618,6 +2722,11 @@ void lokit_main(
|
|||
|
||||
linkOrCopy(loTemplate, loJailDestPath, linkablePath, LinkOrCopyType::LO);
|
||||
|
||||
#if CODE_COVERAGE
|
||||
// Link the .gcda files.
|
||||
linkGCDAFiles(jailPathStr);
|
||||
#endif
|
||||
|
||||
// Update the dynamic files inside the jail.
|
||||
if (!JailUtil::SysTemplate::updateDynamicFiles(jailPathStr))
|
||||
{
|
||||
|
|
|
@ -24,7 +24,7 @@ public:
|
|||
: UnitWSD("UnitTimeout")
|
||||
, _timedOut(false)
|
||||
{
|
||||
setTimeout(std::chrono::milliseconds(10));
|
||||
setTimeout(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
virtual void timeout() override
|
||||
|
|
|
@ -2268,6 +2268,10 @@ void COOLWSD::innerInitialize(Application& self)
|
|||
if (ChildRoot[ChildRoot.size() - 1] != '/')
|
||||
ChildRoot += '/';
|
||||
|
||||
#if CODE_COVERAGE
|
||||
::setenv("BASE_CHILD_ROOT", Poco::Path(ChildRoot).absolute().toString().c_str(), 1);
|
||||
#endif
|
||||
|
||||
// Create a custom sub-path for parallelized unit tests.
|
||||
if (UnitBase::isUnitTesting())
|
||||
{
|
||||
|
@ -2307,9 +2311,18 @@ void COOLWSD::innerInitialize(Application& self)
|
|||
// Initialize the config subsystem too.
|
||||
config::initialize(&config());
|
||||
|
||||
bool bindMount = getConfigValue<bool>(conf, "mount_jail_tree", true);
|
||||
#if CODE_COVERAGE
|
||||
// Code coverage is not supported with bind-mounting.
|
||||
if (bindMount)
|
||||
{
|
||||
LOG_WRN("Mounting is not compatible with code-coverage. Disabling.");
|
||||
bindMount = false;
|
||||
}
|
||||
#endif // CODE_COVERAGE
|
||||
|
||||
// Setup the jails.
|
||||
JailUtil::setupChildRoot(getConfigValue<bool>(conf, "mount_jail_tree", true), ChildRoot,
|
||||
SysTemplate);
|
||||
JailUtil::setupChildRoot(bindMount, ChildRoot, SysTemplate);
|
||||
|
||||
LOG_DBG("FileServerRoot before config: " << FileServerRoot);
|
||||
FileServerRoot = getPathFromConfig("file_server_root_path");
|
||||
|
@ -5436,7 +5449,12 @@ int COOLWSD::innerMain()
|
|||
#if !defined(KIT_IN_PROCESS) && !MOBILEAPP
|
||||
// Terminate child processes
|
||||
LOG_INF("Requesting forkit process " << ForKitProcId << " to terminate.");
|
||||
SigUtil::killChild(ForKitProcId, SIGKILL);
|
||||
#if CODE_COVERAGE
|
||||
constexpr auto signal = SIGTERM;
|
||||
#else
|
||||
constexpr auto signal = SIGKILL;
|
||||
#endif
|
||||
SigUtil::killChild(ForKitProcId, signal);
|
||||
#endif
|
||||
|
||||
Server->stopPrisoners();
|
||||
|
@ -5559,6 +5577,11 @@ int COOLWSD::main(const std::vector<std::string>& /*args*/)
|
|||
UnitWSD::get().returnValue(returnValue);
|
||||
|
||||
LOG_INF("Process [coolwsd] finished with exit status: " << returnValue);
|
||||
|
||||
#if CODE_COVERAGE
|
||||
__gcov_dump();
|
||||
#endif
|
||||
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
|
|
@ -117,7 +117,12 @@ public:
|
|||
if (::kill(_pid, 0) == 0)
|
||||
{
|
||||
LOG_INF("Killing child [" << _pid << "].");
|
||||
if (!SigUtil::killChild(_pid, SIGKILL))
|
||||
#if CODE_COVERAGE
|
||||
constexpr auto signal = SIGTERM;
|
||||
#else
|
||||
constexpr auto signal = SIGKILL;
|
||||
#endif
|
||||
if (!SigUtil::killChild(_pid, signal))
|
||||
{
|
||||
LOG_ERR("Cannot terminate lokit [" << _pid << "]. Abandoning.");
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue