xaizek / zograscope (License: AGPLv3 only) (since 2018-12-07)
Mainly a syntax-aware diff that also provides a number of additional tools.
<root> / tests / tests.hpp (eae133d1ce7d30b86427b4645c8b2eee8a4381fa) (7,417B) (mode 100644) [raw]
// Copyright (C) 2017 xaizek <xaizek@posteo.net>
//
// This file is part of zograscope.
//
// zograscope 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.
//
// zograscope 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 zograscope.  If not, see <http://www.gnu.org/licenses/>.

#ifndef ZOGRASCOPE_TESTS_TESTS_HPP_
#define ZOGRASCOPE_TESTS_TESTS_HPP_

#include <cstdint>
#include <cstdlib>

#include <functional>
#include <ostream>
#include <sstream>
#include <string>
#include <vector>

#include <boost/filesystem/operations.hpp>

class Node;
class Tree;

enum class SType : std::uint8_t;
enum class State : std::uint8_t;
enum class Type : std::uint8_t;

/**
 * @brief Temporarily redirects specified stream into a string.
 */
class StreamCapture
{
public:
    /**
     * @brief Constructs instance that redirects @p os.
     *
     * @param os Stream to redirect.
     */
    StreamCapture(std::ostream &os) : os(os)
    {
        rdbuf = os.rdbuf();
        os.rdbuf(oss.rdbuf());
    }

    /**
     * @brief Restores original state of the stream.
     */
    ~StreamCapture()
    {
        os.rdbuf(rdbuf);
    }

public:
    /**
     * @brief Retrieves captured output collected so far.
     *
     * @returns String containing the output.
     */
    std::string get() const
    {
        return oss.str();
    }

private:
    std::ostream &os;       //!< Stream that is being redirected.
    std::ostringstream oss; //!< Temporary output buffer of the stream.
    std::streambuf *rdbuf;  //!< Original output buffer of the stream.
};

// Temporary directory in RAII-style.
class TempDir
{
public:
    // Makes temporary directory, which is removed in destructor.
    explicit TempDir(const std::string &prefix);

    // Make sure temporary directory is deleted only once.
    TempDir(const TempDir &rhs) = delete;
    TempDir & operator=(const TempDir &rhs) = delete;

    // Removes temporary directory and all its content, if it still exists.
    ~TempDir();

public:
    // Provides implicit conversion to a directory path string.
    operator std::string() const
    { return path; }

    // Explicit conversion to a directory path string.
    const std::string & str() const
    { return path; }

private:
    std::string path; // Path to the temporary directory.
};

class Chdir
{
public:
    explicit Chdir(const std::string &where)
        : previousPath(boost::filesystem::current_path())
    { boost::filesystem::current_path(where); }

    Chdir(const Chdir &rhs) = delete;
    Chdir & operator=(const Chdir &rhs) = delete;

    ~Chdir()
    {
        boost::system::error_code ec;
        boost::filesystem::current_path(previousPath, ec);
    }

private:
    const boost::filesystem::path previousPath;
};

// Checks whether C source can be parsed or not.
bool cIsParsed(const std::string &str);

// Checks whether Make source can be parsed or not.
bool makeIsParsed(const std::string &str);

// Parses C source into a tree.
Tree parseC(const std::string &str, bool coarse = false);

// Parses Make source into a tree.
Tree parseMake(const std::string &str);

// Parses C++ source into a tree.
Tree parseCxx(const std::string &str);

// Parses Lua source into a tree.
Tree parseLua(const std::string &str);

// Parses Lua source into a tree.
Tree parseBash(const std::string &str);

// Finds the first node of specified type which has a matching value of its
// label (or any label if `label` is an empty string).
const Node * findNode(const Tree &tree, Type type,
                      const std::string &label = {});

// Finds the first node that matches the predicate.
const Node * findNode(const Tree &tree, std::function<bool(const Node *)> pred,
                      bool skipLastLayer = false);

int countLeaves(const Node &root, State state);

int countInternal(const Node &root, SType stype, State state);

// Diffs two trees and prints result into a normalized string.
std::string compareAndPrint(Tree &&original, Tree &&updated,
                            bool skipRefine = false);

// Strips whitespace and drops empty lines.
std::string normalizeText(const std::string &s);

// Compares two C sources with expectation being embedded in them in form of
// trailing `/// <expectation>` markers.  Returns difference report.
std::string diffC(const std::string &left, const std::string &right,
                  bool skipRefine);
// This is a wrapper that makes reported failure point be somewhere in the test.
#define diffC(left, right, skipRefine) do { \
        std::string difference = diffC((left), (right), (skipRefine)); \
        CHECK(difference.empty()); \
        reportDiffFailure(difference); \
    } while (false)

// Compares two Make sources with expectation being embedded in them in form of
// trailing `## <expectation>` markers.  Returns difference report.
std::string diffMake(const std::string &left, const std::string &right);
// This is a wrapper that makes reported failure point be somewhere in the test.
#define diffMake(left, right) do { \
        std::string difference = diffMake((left), (right)); \
        CHECK(difference.empty()); \
        reportDiffFailure(difference); \
    } while (false)

// Compares two C++ sources with expectation being embedded in them in form of
// trailing `/// <expectation>` markers.  Returns difference report.
std::string diffSrcmlCxx(const std::string &left, const std::string &right);
// This is a wrapper that makes reported failure point be somewhere in the test.
#define diffSrcmlCxx(left, right) do { \
        std::string difference = diffSrcmlCxx((left), (right)); \
        CHECK(difference.empty()); \
        reportDiffFailure(difference); \
    } while (false)

// Compares two Lua sources with expectation being embedded in them in form of
// trailing `--- <expectation>` markers.  Returns difference report.
std::string diffTsLua(const std::string &left, const std::string &right);
// This is a wrapper that makes reported failure point be somewhere in the test.
#define diffTsLua(left, right) do { \
        std::string difference = diffTsLua((left), (right)); \
        CHECK(difference.empty()); \
        reportDiffFailure(difference); \
    } while (false)

// Compares two Bash sources with expectation being embedded in them in form of
// trailing `### <expectation>` markers.  Returns difference report.
std::string diffTsBash(const std::string &left, const std::string &right);
// This is a wrapper that makes reported failure point be somewhere in the test.
#define diffTsBash(left, right) do { \
        std::string difference = diffTsBash((left), (right)); \
        CHECK(difference.empty()); \
        reportDiffFailure(difference); \
    } while (false)

// Prints report.  This function is needed to make our custom output appear
// after Catch's failure report.
void reportDiffFailure(const std::string &report);

// Creates a file with specified contents.
void makeFile(const std::string &path, const std::vector<std::string> &lines);

#endif // ZOGRASCOPE_TESTS_TESTS_HPP_
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/zograscope

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

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