xaizek / uncov (License: AGPLv3+) (since 2018-12-07)
Uncov(er) is a tool that collects and processes code coverage reports.
<root> / src / listings.cpp (21dfdf242f1d153f71fc57f4daa5db659af79d84) (8,845B) (mode 100644) [raw]
// Copyright (C) 2016 xaizek <xaizek@posteo.net>
//
// This file is part of uncov.
//
// uncov is free software: you can redistribute it and/or modify
// it under the terms of version 3 of the GNU Affero General Public License as
// published by the Free Software Foundation.
//
// uncov is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with uncov.  If not, see <http://www.gnu.org/licenses/>.

#include "listings.hpp"

#include <boost/filesystem/path.hpp>
#include <boost/optional.hpp>

#include <map>
#include <ostream>
#include <string>
#include <vector>

#include "utils/fs.hpp"
#include "BuildHistory.hpp"
#include "coverage.hpp"
#include "printing.hpp"

static std::map<std::string, CovInfo>
getDirsCoverage(const Build &build, const std::string &dirFilter);
static CovChange getBuildCovChange(BuildHistory *bh, const Build &build,
                                   const CovInfo &covInfo,
                                   const Build *prevBuild = nullptr);
static void printFileHeader(std::ostream &os, const std::string &filePath,
                            const CovInfo &covInfo, const CovChange &covChange);
static CovChange getFileCovChange(BuildHistory *bh, const Build &build,
                                  const std::string &path,
                                  boost::optional<Build> *prevBuildHint,
                                  const CovInfo &covInfo,
                                  const Build *prevBuild = nullptr);

std::vector<std::string>
describeBuild(BuildHistory *bh, const Build &build, DoExtraAlign extraAlign,
              DoSpacing spacing, const Build *prevBuild)
{
    const std::string sep = spacing ? " / " : "/";
    CovInfo covInfo(build);
    CovChange covChange = getBuildCovChange(bh, build, covInfo, prevBuild);

    return {
        "#" + std::to_string(build.getId()),
        covInfo.formatCoverageRate(),
        covInfo.formatLines(sep),
        covChange.formatCoverageRate(),
        covChange.formatLines(sep, extraAlign ? 4 : 0),
        build.getRefName(),
        Revision{build.getRef()},
        Time{build.getTimestamp()}
    };
}

std::vector<std::vector<std::string>>
describeBuildDirs(BuildHistory *bh, const Build &build,
                  const std::string &dirFilter, const Build *prevBuild)
{
    std::map<std::string, CovInfo> newDirs = getDirsCoverage(build, dirFilter);

    std::map<std::string, CovInfo> prevDirs;
    if (prevBuild != nullptr) {
        prevDirs = getDirsCoverage(*prevBuild, dirFilter);
    } else if (const int prevBuildId = bh->getPreviousBuildId(build.getId())) {
        prevDirs = getDirsCoverage(*bh->getBuild(prevBuildId), dirFilter);
    }

    std::vector<std::vector<std::string>> rows;
    rows.reserve(newDirs.size());

    for (const auto &entry : newDirs) {
        const CovInfo &covInfo = entry.second;
        CovChange covChange(prevDirs[entry.first], covInfo);

        rows.push_back({
            entry.first + '/',
            covInfo.formatCoverageRate(),
            covInfo.formatLines(" / "),
            covChange.formatCoverageRate(),
            covChange.formatLines(" / ", 4)
        });
    }

    return rows;
}

static std::map<std::string, CovInfo>
getDirsCoverage(const Build &build, const std::string &dirFilter)
{
    std::map<std::string, CovInfo> dirs;
    for (const std::string &filePath : build.getPaths()) {
        if (!pathIsInSubtree(dirFilter, filePath)) {
            continue;
        }

        boost::filesystem::path dirPath = filePath;
        dirPath.remove_filename();

        File &file = *build.getFile(filePath);
        CovInfo &info = dirs[dirPath.string()];

        info.add(CovInfo(file));
    }
    return dirs;
}

std::vector<std::vector<std::string>>
describeBuildFiles(BuildHistory *bh, const Build &build,
                   const std::string &dirFilter, ListChangedOnly changedOnly,
                   ListDirectOnly directOnly, const Build *prevBuild)
{
    const std::vector<std::string> &paths = build.getPaths();

    std::vector<std::vector<std::string>> rows;
    rows.reserve(paths.size());

    boost::optional<Build> prev;
    if (prevBuild != nullptr) {
        prev = *prevBuild;
    } else if (const int prevBuildId = bh->getPreviousBuildId(build.getId())) {
        prev = bh->getBuild(prevBuildId);
    }

    boost::filesystem::path dpath = dirFilter;
    for (const std::string &filePath : paths) {
        boost::filesystem::path fpath = filePath;

        if (!pathIsInSubtree(dpath, fpath)) {
            continue;
        }

        if (directOnly && fpath.parent_path() != dpath) {
            continue;
        }

        CovInfo covInfo(*build.getFile(filePath));
        CovChange covChange = getFileCovChange(bh, build, filePath, &prev,
                                               covInfo);

        if (changedOnly && !covChange.isChanged()) {
            continue;
        }

        rows.push_back({
            directOnly ? fpath.filename().string() : filePath,
            covInfo.formatCoverageRate(),
            covInfo.formatLines(" / "),
            covChange.formatCoverageRate(),
            covChange.formatLines(" / ", 4),
        });
    }

    return rows;
}

void
printBuildHeader(std::ostream &os, BuildHistory *bh, const Build &build,
                 const Build *prevBuild)
{
    std::vector<std::string> v = describeBuild(bh, build, DoExtraAlign{},
                                               !DoSpacing{}, prevBuild);
    os << Label{"Build"} << ": " << v[0] << ", "
       << v[1] << '(' << v[2] << "), "
       << v[3] << '(' << v[4] << "), "
       << v[5] << '\n';
}

static CovChange
getBuildCovChange(BuildHistory *bh, const Build &build, const CovInfo &covInfo,
                  const Build *prevBuild)
{
    CovInfo prevCovInfo;
    if (prevBuild != nullptr) {
        prevCovInfo = CovInfo(*prevBuild);
    } else if (const int prevBuildId = bh->getPreviousBuildId(build.getId())) {
        prevCovInfo = CovInfo(*bh->getBuild(prevBuildId));
    }

    return CovChange(prevCovInfo, covInfo);
}

void
printFileHeader(std::ostream &os, BuildHistory *bh, const Build &build,
                const File &file)
{
    std::vector<std::string> v = describeFile(bh, build, file, !DoSpacing{});
    os << Label{"File"} << ": " << v[0] << ", "
       << v[1] << '(' << v[2] << "), "
       << v[3] << '(' << v[4] << ")\n";
}

std::vector<std::string>
describeFile(BuildHistory *bh, const Build &build, const File &file,
             DoSpacing spacing)
{
    const std::string sep = spacing ? " / " : "/";
    CovInfo covInfo(file);
    CovChange covChange = getFileCovChange(bh, build, file.getPath(), nullptr,
                                           covInfo);

    return {
        file.getPath(),
        covInfo.formatCoverageRate(),
        covInfo.formatLines(sep),
        covChange.formatCoverageRate(),
        covChange.formatLines(sep),
    };
}

void
printFileHeader(std::ostream &os, BuildHistory *bh, const Build &build,
                const std::string &filePath, const Build *prevBuild)
{
    CovInfo covInfo;
    if (boost::optional<File &> file = build.getFile(filePath)) {
        covInfo = CovInfo(*file);
    }

    CovChange covChange = getFileCovChange(bh, build, filePath, nullptr,
                                           covInfo, prevBuild);
    printFileHeader(os, filePath, covInfo, covChange);
}

static void
printFileHeader(std::ostream &os, const std::string &filePath,
                const CovInfo &covInfo, const CovChange &covChange)
{
    os << Label{"File"} << ": " << filePath << ", "
       << covInfo.formatCoverageRate() << ' '
       << '(' << covInfo.formatLines("/") << "), "
       << covChange.formatCoverageRate() << ' '
       << '(' << covChange.formatLines("/") << ")\n";
}

static CovChange
getFileCovChange(BuildHistory *bh, const Build &build, const std::string &path,
                 boost::optional<Build> *prevBuildHint, const CovInfo &covInfo,
                 const Build *prevBuild)
{
    boost::optional<Build> prevBuildStorage;
    if (prevBuildHint == nullptr) {
        if (prevBuild != nullptr) {
            prevBuildStorage = *prevBuild;
        } else if (int prevBuildId = bh->getPreviousBuildId(build.getId())) {
            prevBuildStorage = bh->getBuild(prevBuildId);
        }
        prevBuildHint = &prevBuildStorage;
    }

    CovInfo prevCovInfo;
    if (*prevBuildHint) {
        if (boost::optional<File &> file = (*prevBuildHint)->getFile(path)) {
            prevCovInfo = CovInfo(*file);
        }
    }

    return CovChange(prevCovInfo, covInfo);
}
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/uncov

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/uncov

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master