Skip to content

Commit

Permalink
Issue #224: bash-engine: Show uncovered scripts in report
Browse files Browse the repository at this point in the history
By default kcov scans the binary directory for bash scripts.
  • Loading branch information
SimonKagstrom committed Nov 3, 2017
1 parent 18d4938 commit 0dd22bd
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 2 deletions.
7 changes: 7 additions & 0 deletions doc/kcov.1
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,13 @@ the PS4 environment variable, or DEBUG for use of the DEBUG trap.
Handle invocations of /bin/sh scripts via using a LD_PRELOADed library that replaces execve (i.e., /bin/sh is
executed as /bin/bash). Does not work well on some systems, so the default is not to use this.
.TP
\fB\-\-bash\-dont\-parse\-binary\-dir
Kcov parses the directory of the binary for other scripts and add these to the report. If you don't
want this behavior, this option turns that off.
.TP
\fB\-\-bash\-parse\-files\-in\-dir\fP=\fIP1\fP[\fI,P2\fP...]
Parse directories for bash scripts.
.TP
\fB\-\-replace\-src\-path\fP=\fIP1\fP:\fIP2\fP
Replace source file path P1 with P2, if found.
.TP
Expand Down
18 changes: 17 additions & 1 deletion src/configuration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class Configuration : public IConfiguration
{"debug", required_argument, 0, 'D'},
{"debug-force-bash-stderr", no_argument, 0, 'd'},
{"bash-handle-sh-invocation", no_argument, 0, 's'},
{"bash-dont-parse-binary-dir", no_argument, 0, 'j'},
{"bash-parse-files-in-dirs", required_argument, 0, 'J'},
{"replace-src-path", required_argument, 0, 'R'},
{"collect-only", no_argument, 0, 'C'},
{"report-only", no_argument, 0, 'r'},
Expand Down Expand Up @@ -402,6 +404,16 @@ class Configuration : public IConfiguration
setKey("high-limit", stoul(vec[1]));
break;
}
case 'j':
setKey("bash-parse-binary-dir", 0);
break;
case 'J':
{
StrVecMap_t bashFilesInPath = getCommaSeparatedList(std::string(optarg));

setKey("bash-parse-file-dir", bashFilesInPath);
break;
}
case 'R': {
std::string tmpArg = std::string(optarg);
size_t tokenPosFront = tmpArg.find_first_of(":");
Expand Down Expand Up @@ -551,6 +563,8 @@ class Configuration : public IConfiguration
setKey("bash-handle-sh-invocation", 0);
setKey("bash-use-basic-parser", 0);
setKey("bash-use-ps4", 1);
setKey("bash-parse-file-dir", StrVecMap_t());
setKey("bash-parse-binary-dir", 1);
setKey("verify", 0);
setKey("command-name", "");
setKey("merged-name", "[merged]");
Expand Down Expand Up @@ -672,7 +686,9 @@ class Configuration : public IConfiguration
" default: %s\n"
" --bash-method=method Bash coverage collection method, PS4 (default) or DEBUG\n"
" --bash-handle-sh-invocation Try to handle #!/bin/sh scripts by a LD_PRELOAD\n"
" execve replacement. Buggy on some systems\n",
" execve replacement. Buggy on some systems\n"
" --bash-dont-parse-binary-dir Don't parse the binary directory for other scripts\n"
" --bash-parse-files-in-dir=dir[,...] Parse bash scripts in dir(s)\n",
keyAsInt("path-strip-level"), keyAsInt("output-interval"),
getConfigurableValues(),
keyAsString("python-command").c_str(), keyAsString("bash-command").c_str(),
Expand Down
68 changes: 67 additions & 1 deletion src/engines/bash-engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#include <sys/stat.h>
#include <sys/wait.h>
#include <signal.h>
#include <dirent.h>

#include <list>
#include <unordered_map>
Expand Down Expand Up @@ -57,6 +58,7 @@ class BashEngine : public ScriptEngineBase

bool start(IEventListener &listener, const std::string &executable)
{
IConfiguration &conf = IConfiguration::getInstance();
int stderrPipe[2];
int stdoutPipe[2];

Expand Down Expand Up @@ -127,7 +129,6 @@ class BashEngine : public ScriptEngineBase
}

} else if (m_child == 0) {
IConfiguration &conf = IConfiguration::getInstance();
const char **argv = conf.getArgv();
unsigned int argc = conf.getArgc();
int xtraceFd = 782; // Typical bash users use 3,4 etc but not high fd numbers (?)
Expand Down Expand Up @@ -201,6 +202,21 @@ class BashEngine : public ScriptEngineBase
return false;
}


std::vector<std::string> dirsToParse = conf.keyAsList("bash-parse-file-dir");

for (std::vector<std::string>::iterator it = dirsToParse.begin();
it != dirsToParse.end();
++it)
{
parseDirectoryForFiles(*it);
}

// Parse the directory where the script-under-test is for other bash scripts
if (conf.keyAsInt("bash-parse-binary-dir"))
{
parseDirectoryForFiles(conf.keyAsString("binary-path"));
}
parseFile(executable);

return true;
Expand Down Expand Up @@ -349,6 +365,56 @@ class BashEngine : public ScriptEngineBase


private:
void parseDirectoryForFiles(const std::string &base)
{
DIR *dir = ::opendir(base.c_str());
if (!dir)
{
error("Can't open directory %s\n", base.c_str());
return;
}

// Loop through the directory structure
struct dirent *de;
for (de = ::readdir(dir); de; de = ::readdir(dir))
{
std::string cur = base + "/" + de->d_name;

if (strcmp(de->d_name, ".") == 0)
continue;

if (strcmp(de->d_name, "..") == 0)
continue;

struct stat st;

if (lstat(cur.c_str(), &st) < 0)
continue;

if (S_ISDIR(st.st_mode))
{
parseDirectoryForFiles(cur);
}
else
{
size_t sz;
uint8_t *p = (uint8_t *)read_file(&sz, "%s", cur.c_str());

if (p)
{
// Bash file?
if (matchParser(cur, p, sz) != match_none)
{
parseFile(cur);
}

}
free((void *)p);
}
}
::closedir(dir);
}

// Printout lines to stdout, except kcov markers
void handleStdout()
{
Expand Down
32 changes: 32 additions & 0 deletions tests/tools/bash.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,35 @@ def runTest(self):
assert parse_cobertura.hitsPerLine(dom, "other.sh", 48) == None
assert parse_cobertura.hitsPerLine(dom, "other.sh", 49) == None
assert parse_cobertura.hitsPerLine(dom, "other.sh", 51) == 1

# Issue #224
class bash_can_find_non_executed_scripts(testbase.KcovTestCase):
def runTest(self):
self.setUp()
rv,o = self.do(testbase.kcov + " " + testbase.outbase + "/kcov " + testbase.sources + "/tests/bash/first-dir/a.sh 5")

dom = parse_cobertura.parseFile(testbase.outbase + "/kcov/a.sh/cobertura.xml")
assert parse_cobertura.hitsPerLine(dom, "a.sh", 5) == 1
# Not executed
assert parse_cobertura.hitsPerLine(dom, "c.sh", 3) == 0

class bash_can_find_non_executed_scripts_manually(testbase.KcovTestCase):
def runTest(self):
self.setUp()
rv,o = self.do(testbase.kcov + " --bash-parse-files-in-dir=" + testbase.sources + "/tests/bash " + testbase.outbase + "/kcov " + testbase.sources + "/tests/bash/first-dir/a.sh 5")

dom = parse_cobertura.parseFile(testbase.outbase + "/kcov/a.sh/cobertura.xml")
# Not executed
assert parse_cobertura.hitsPerLine(dom, "c.sh", 3) == 0
assert parse_cobertura.hitsPerLine(dom, "other.sh", 3) == 0

class bash_can_ignore_non_executed_scripts(testbase.KcovTestCase):
def runTest(self):
self.setUp()
rv,o = self.do(testbase.kcov + " --bash-dont-parse-binary-dir " + testbase.outbase + "/kcov " + testbase.sources + "/tests/bash/first-dir/a.sh 5")

dom = parse_cobertura.parseFile(testbase.outbase + "/kcov/a.sh/cobertura.xml")
assert parse_cobertura.hitsPerLine(dom, "a.sh", 5) == 1
# Not included in report
assert parse_cobertura.hitsPerLine(dom, "c.sh", 3) == None
assert parse_cobertura.hitsPerLine(dom, "other.sh", 3) == None

0 comments on commit 0dd22bd

Please sign in to comment.