mirror of
https://github.com/emilybache/GildedRose-Refactoring-Kata.git
synced 2025-12-12 04:12:13 +00:00
* Rename the library under test from src to GildedRoseLib. * Move the lib folder to test/third_party and handle ApprovalTests.hpp as header-only dependency. * Remove the superfluous googletest_approval_main.cpp (in the same sense as there is only one GildedRoseCatch2ApprovalTests.cc) * Set USE_FOLDERS property such that target ALL_BUILD, RUN_TESTS and ZERO_CHECK are grouped for Visual Studio. * Set WORKING_DIRECTORY such that the RUN_TESTS target works successfully. * Fix C++ language standard for Catch2 v3 to C++14. * Bump third-party dependencies * Update ApprovalTests.hpp to v10.13.0 * Update Catch2 to v3.8.0 * Update GTest to v1.16.0
6898 lines
200 KiB
C++
6898 lines
200 KiB
C++
// ApprovalTests.cpp version v.10.13.0
|
|
// More information at: https://github.com/approvals/ApprovalTests.cpp
|
|
//
|
|
// Copyright (c) 2024 Llewellyn Falco and Clare Macrae. All rights reserved.
|
|
//
|
|
// Distributed under the Apache 2.0 License
|
|
// See https://opensource.org/licenses/Apache-2.0
|
|
|
|
//----------------------------------------------------------------------
|
|
// Welcome to Approval Tests.
|
|
//
|
|
// If you experience linker errors about missing symbols, it means
|
|
// you have forgotten to configure your test framework for Approval Tests.
|
|
//
|
|
// For help with this, please see:
|
|
// https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredMain.md
|
|
//----------------------------------------------------------------------
|
|
|
|
// ******************** From: ApprovalTests.hpp
|
|
#ifndef APPROVAL_TESTS_CPP_APPROVALS_HPP
|
|
#define APPROVAL_TESTS_CPP_APPROVALS_HPP
|
|
|
|
// This file is machine-generated. Do not edit.
|
|
|
|
|
|
// ******************** From: ApprovalTestsVersion.h
|
|
|
|
#define APPROVAL_TESTS_VERSION_MAJOR 10
|
|
#define APPROVAL_TESTS_VERSION_MINOR 13
|
|
#define APPROVAL_TESTS_VERSION_PATCH 0
|
|
#define APPROVAL_TESTS_VERSION_STR "10.13.0"
|
|
|
|
#define APPROVAL_TESTS_VERSION \
|
|
(APPROVAL_TESTS_VERSION_MAJOR * 10000 + APPROVAL_TESTS_VERSION_MINOR * 100 + \
|
|
APPROVAL_TESTS_VERSION_PATCH)
|
|
|
|
// ******************** From: Reporter.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Reporter
|
|
{
|
|
public:
|
|
virtual ~Reporter() = default;
|
|
virtual bool report(std::string received, std::string approved) const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ReporterFactory.h
|
|
|
|
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ReporterFactory
|
|
{
|
|
public:
|
|
using Reporters =
|
|
std::map<std::string, std::function<std::unique_ptr<Reporter>()>>;
|
|
|
|
ReporterFactory();
|
|
|
|
std::vector<std::string> allSupportedReporterNames() const;
|
|
|
|
std::unique_ptr<Reporter> createReporter(const std::string& reporterName) const;
|
|
|
|
std::string findReporterName(const std::string& osPrefix,
|
|
const std::string& reporterName) const;
|
|
|
|
private:
|
|
static Reporters createMap();
|
|
|
|
Reporters map;
|
|
};
|
|
}
|
|
|
|
// ******************** From: DiffInfo.h
|
|
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
enum class Type
|
|
{
|
|
TEXT,
|
|
IMAGE,
|
|
TEXT_AND_IMAGE
|
|
};
|
|
|
|
struct DiffInfo
|
|
{
|
|
static std::string receivedFileTemplate();
|
|
|
|
static std::string approvedFileTemplate();
|
|
|
|
static std::string programFileTemplate();
|
|
|
|
static std::string getDefaultArguments();
|
|
|
|
DiffInfo(std::string program_, Type type_);
|
|
|
|
DiffInfo(std::string program_, std::string arguments_, Type type_);
|
|
|
|
std::string program;
|
|
std::string arguments;
|
|
Type type;
|
|
|
|
static std::vector<std::string> getProgramFileLocations();
|
|
|
|
std::string getProgramForOs() const;
|
|
};
|
|
}
|
|
|
|
// ******************** From: DiffPrograms.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace DiffPrograms
|
|
{
|
|
namespace CrossPlatform
|
|
{
|
|
DiffInfo VS_CODE();
|
|
}
|
|
|
|
namespace Mac
|
|
{
|
|
DiffInfo DIFF_MERGE();
|
|
|
|
DiffInfo ARAXIS_MERGE();
|
|
|
|
DiffInfo BEYOND_COMPARE();
|
|
|
|
DiffInfo KALEIDOSCOPE();
|
|
|
|
DiffInfo SUBLIME_MERGE();
|
|
|
|
DiffInfo KDIFF3();
|
|
|
|
DiffInfo P4MERGE();
|
|
|
|
DiffInfo TK_DIFF();
|
|
|
|
DiffInfo VS_CODE();
|
|
|
|
DiffInfo CLION();
|
|
}
|
|
|
|
namespace Linux
|
|
{
|
|
DiffInfo SUBLIME_MERGE_SNAP();
|
|
|
|
DiffInfo SUBLIME_MERGE_FLATPAK();
|
|
|
|
DiffInfo SUBLIME_MERGE_REPOSITORY_PACKAGE();
|
|
|
|
DiffInfo SUBLIME_MERGE_DIRECT_DOWNLOAD();
|
|
|
|
// More ideas available from: https://www.tecmint.com/best-linux-file-diff-tools-comparison/
|
|
DiffInfo KDIFF3();
|
|
|
|
DiffInfo MELD();
|
|
|
|
DiffInfo BEYOND_COMPARE();
|
|
}
|
|
|
|
namespace Windows
|
|
{
|
|
DiffInfo BEYOND_COMPARE_3();
|
|
|
|
DiffInfo BEYOND_COMPARE_4();
|
|
|
|
DiffInfo TORTOISE_IMAGE_DIFF();
|
|
|
|
DiffInfo TORTOISE_TEXT_DIFF();
|
|
|
|
DiffInfo TORTOISE_GIT_IMAGE_DIFF();
|
|
|
|
DiffInfo TORTOISE_GIT_TEXT_DIFF();
|
|
|
|
DiffInfo WIN_MERGE_REPORTER();
|
|
|
|
DiffInfo ARAXIS_MERGE();
|
|
|
|
DiffInfo CODE_COMPARE();
|
|
|
|
DiffInfo SUBLIME_MERGE();
|
|
|
|
DiffInfo KDIFF3();
|
|
|
|
DiffInfo VS_CODE();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: ConvertForCygwin.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ConvertForCygwin
|
|
{
|
|
public:
|
|
virtual ~ConvertForCygwin() = default;
|
|
|
|
virtual std::string convertProgramForCygwin(const std::string& filePath);
|
|
|
|
virtual std::string convertFileArgumentForCygwin(const std::string& filePath);
|
|
};
|
|
|
|
class DoNothing : public ConvertForCygwin
|
|
{
|
|
public:
|
|
std::string convertProgramForCygwin(const std::string& filePath) override;
|
|
|
|
std::string convertFileArgumentForCygwin(const std::string& filePath) override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: CommandLauncher.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// An interface to trigger execution of a command. See also SystemLauncher
|
|
class CommandLauncher
|
|
{
|
|
public:
|
|
virtual ~CommandLauncher() = default;
|
|
virtual bool launch(const std::string& commandLine) = 0;
|
|
virtual std::string getCommandLine(const std::string& commandLine) const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: CommandReporter.h
|
|
|
|
#include <utility>
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// Generic reporter to launch arbitrary command
|
|
class CommandReporter : public Reporter
|
|
{
|
|
private:
|
|
std::string cmd;
|
|
std::string arguments = DiffInfo::getDefaultArguments();
|
|
CommandLauncher* l;
|
|
std::shared_ptr<ConvertForCygwin> converter;
|
|
|
|
std::string assembleFullCommand(const std::string& received,
|
|
const std::string& approved) const;
|
|
|
|
protected:
|
|
CommandReporter(std::string command, CommandLauncher* launcher);
|
|
|
|
CommandReporter(std::string command, std::string args, CommandLauncher* launcher);
|
|
|
|
public:
|
|
static bool exists(const std::string& command);
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
|
|
std::string getCommandLine(const std::string& received,
|
|
const std::string& approved) const;
|
|
|
|
public:
|
|
void checkForCygwin();
|
|
|
|
// Seam for testing
|
|
void useCygwinConversions(bool useCygwin);
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalsMacroDefaults.h
|
|
|
|
// This file intentionally left blank.
|
|
|
|
// ******************** From: Macros.h
|
|
|
|
|
|
// Use this in places where we have parameters that are sometimes unused,
|
|
// e.g. because of #if
|
|
// See https://stackoverflow.com/a/1486931/104370
|
|
#define APPROVAL_TESTS_UNUSED(expr) \
|
|
do \
|
|
{ \
|
|
(void)(expr); \
|
|
} while (0)
|
|
|
|
#if __cplusplus >= 201703L
|
|
#define APPROVAL_TESTS_NO_DISCARD [[nodiscard]]
|
|
#else
|
|
#define APPROVAL_TESTS_NO_DISCARD
|
|
#endif
|
|
|
|
#if (__cplusplus >= 201402L)
|
|
#define APPROVAL_TESTS_DEPRECATED(text) [[deprecated(text)]]
|
|
#define APPROVAL_TESTS_DEPRECATED_CPP11(text)
|
|
#else
|
|
#define APPROVAL_TESTS_DEPRECATED(text)
|
|
#define APPROVAL_TESTS_DEPRECATED_CPP11(text) \
|
|
MoreHelpMessages::deprecatedFunctionCalled(text, __FILE__, __LINE__);
|
|
#endif
|
|
|
|
#define APPROVAL_TESTS_REGISTER_MAIN_DIRECTORY \
|
|
auto approval_tests_registered_main_directory = \
|
|
ApprovalTests::TestName::registerRootDirectoryFromMainFile(__FILE__);
|
|
|
|
// ******************** From: EmptyFileCreatorFactory.h
|
|
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
using EmptyFileCreator = std::function<void(std::string)>;
|
|
|
|
class EmptyFileCreatorFactory
|
|
{
|
|
public:
|
|
static void defaultCreator(std::string fullFilePath);
|
|
static EmptyFileCreator currentCreator;
|
|
};
|
|
}
|
|
|
|
// ******************** From: EmptyFileCreatorDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class APPROVAL_TESTS_NO_DISCARD EmptyFileCreatorDisposer
|
|
{
|
|
private:
|
|
EmptyFileCreator previous_result;
|
|
|
|
public:
|
|
explicit EmptyFileCreatorDisposer(EmptyFileCreator creator);
|
|
EmptyFileCreatorDisposer(const EmptyFileCreatorDisposer&) = default;
|
|
|
|
~EmptyFileCreatorDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: FileUtils.h
|
|
|
|
#include <string>
|
|
namespace ApprovalTests
|
|
{
|
|
class FileUtils
|
|
{
|
|
public:
|
|
static bool fileExists(const std::string& path);
|
|
|
|
static int fileSize(const std::string& path);
|
|
|
|
static EmptyFileCreatorDisposer useEmptyFileCreator(EmptyFileCreator creator);
|
|
|
|
static void ensureFileExists(const std::string& fullFilePath);
|
|
|
|
static std::string getDirectory(const std::string& filePath);
|
|
|
|
static std::string getExtensionWithDot(const std::string& filePath);
|
|
|
|
static std::string readFileThrowIfMissing(const std::string& fileName);
|
|
|
|
static std::string readFileReturnEmptyIfMissing(const std::string& fileName);
|
|
|
|
static void writeToFile(const std::string& filePath, const std::string& content);
|
|
};
|
|
}
|
|
|
|
// ******************** From: WinMinGWUtils.h
|
|
|
|
#if (defined(__MINGW32__) || defined(__MINGW64__))
|
|
#define APPROVAL_TESTS_MINGW
|
|
#endif
|
|
|
|
// ******************** From: StringMaker.h
|
|
|
|
#include <string>
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class StringMaker
|
|
{
|
|
public:
|
|
template <typename T> static std::string toString(const T& contents)
|
|
{
|
|
std::stringstream s;
|
|
s << contents;
|
|
return s.str();
|
|
}
|
|
};
|
|
}
|
|
|
|
// ******************** From: StringUtils.h
|
|
|
|
|
|
|
|
|
|
#include <string>
|
|
#include <algorithm>
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class StringUtils
|
|
{
|
|
public:
|
|
static std::string replaceAll(std::string inText,
|
|
const std::string& find,
|
|
const std::string& replaceWith);
|
|
|
|
static bool contains(const std::string& inText, const std::string& find);
|
|
|
|
static std::string toLower(std::string inText);
|
|
|
|
static bool beginsWith(std::string value, std::string beginning);
|
|
static bool endsWith(std::string value, std::string ending);
|
|
|
|
template <typename T> static std::string toString(const T& contents)
|
|
{
|
|
return StringMaker::toString(contents);
|
|
}
|
|
|
|
static std::string leftTrim(std::string s);
|
|
|
|
static std::string rightTrim(std::string s);
|
|
|
|
static std::string trim(std::string s);
|
|
};
|
|
}
|
|
|
|
// ******************** From: SystemUtils.h
|
|
|
|
#ifdef _WIN32
|
|
// ReSharper disable once CppUnusedIncludeDirective
|
|
#include <io.h>
|
|
#include <direct.h>
|
|
#else
|
|
// ReSharper disable once CppUnusedIncludeDirective
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
|
|
#include <stdexcept>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class SystemUtils
|
|
{
|
|
public:
|
|
static bool isWindowsOs();
|
|
|
|
static bool isCygwin();
|
|
|
|
static bool isMacOs();
|
|
|
|
static std::string getDirectorySeparator();
|
|
|
|
// Properly cases the filename, but not the directories, on Windows.
|
|
static std::string checkFilenameCase(const std::string& fullPath);
|
|
|
|
static std::string safeGetEnvForWindows(char const* name);
|
|
|
|
static std::string safeGetEnvForNonWindows(char const* name);
|
|
|
|
//! Return the value of the environment variable, or an empty string if the variable is not set.
|
|
static std::string safeGetEnv(char const* name);
|
|
|
|
static std::string getMachineName();
|
|
|
|
static void makeDirectoryForWindows(const std::string& directory);
|
|
|
|
static void makeDirectoryForNonWindows(const std::string& directory);
|
|
|
|
static void makeDirectory(const std::string& directory);
|
|
|
|
static void ensureDirectoryExists(const std::string& fullDirectoryPath);
|
|
|
|
static void ensureParentDirectoryExists(const std::string& fullFilePath);
|
|
|
|
static void runSystemCommandOrThrow(const std::string& command,
|
|
bool allowNonZeroExitCodes = false);
|
|
};
|
|
}
|
|
|
|
// ******************** From: SystemLauncher.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class SystemLauncher : public CommandLauncher
|
|
{
|
|
private:
|
|
bool useWindows_ = SystemUtils::isWindowsOs();
|
|
bool isForeground_ = false;
|
|
bool allowNonZeroExitCodes_ = false;
|
|
|
|
public:
|
|
explicit SystemLauncher(bool isForeground = false,
|
|
bool allowNonZeroExitCodes = false);
|
|
|
|
bool launch(const std::string& commandLine) override;
|
|
|
|
// Seam for testing
|
|
void invokeForWindows(bool useWindows);
|
|
|
|
void setForeground(bool foreground);
|
|
|
|
void setAllowNonZeroExitCodes(bool allow);
|
|
|
|
bool isForeground() const;
|
|
|
|
std::string getCommandLine(const std::string& commandLine) const override;
|
|
|
|
std::string getWindowsCommandLine(const std::string& commandLine,
|
|
bool foreground) const;
|
|
|
|
std::string getUnixCommandLine(const std::string& commandLine,
|
|
bool foreground) const;
|
|
};
|
|
}
|
|
|
|
// ******************** From: GenericDiffReporter.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class GenericDiffReporter : public CommandReporter
|
|
{
|
|
public:
|
|
SystemLauncher launcher;
|
|
|
|
public:
|
|
explicit GenericDiffReporter(const std::string& program);
|
|
|
|
explicit GenericDiffReporter(const DiffInfo& info);
|
|
};
|
|
}
|
|
|
|
// ******************** From: QuietReporter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// A reporter that does nothing. Failing tests will still fail, but nothing will be launched.
|
|
class QuietReporter : public Reporter
|
|
{
|
|
public:
|
|
bool report(std::string /*received*/, std::string /*approved*/) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: TextDiffReporter.h
|
|
|
|
|
|
#include <memory>
|
|
#include <iosfwd>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// A Reporter class that only uses text-based diff tools, with output written
|
|
// to the console. It provides no opportunity for interactive approving
|
|
// of files.
|
|
// It currently has a short, hard-coded list of diffing tools.
|
|
class TextDiffReporter : public Reporter
|
|
{
|
|
private:
|
|
std::unique_ptr<Reporter> m_reporter;
|
|
std::ostream& stream_;
|
|
|
|
public:
|
|
TextDiffReporter();
|
|
explicit TextDiffReporter(std::ostream& stream);
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
} // namespace ApprovalTests
|
|
|
|
// ******************** From: Blocker.h
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Blocker
|
|
{
|
|
public:
|
|
Blocker() = default;
|
|
Blocker(const Blocker&) = default;
|
|
Blocker(Blocker&&) = default;
|
|
Blocker& operator=(const Blocker&) = default;
|
|
Blocker& operator=(Blocker&&) = default;
|
|
|
|
virtual ~Blocker() = default;
|
|
|
|
virtual bool isBlockingOnThisMachine() const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: MachineBlocker.h
|
|
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class MachineBlocker : public Blocker
|
|
{
|
|
private:
|
|
std::string machineName;
|
|
bool block;
|
|
|
|
MachineBlocker() = delete;
|
|
|
|
public:
|
|
MachineBlocker(std::string machineName_, bool block_);
|
|
|
|
static MachineBlocker onMachineNamed(const std::string& machineName);
|
|
|
|
static MachineBlocker onMachinesNotNamed(const std::string& machineName);
|
|
|
|
virtual bool isBlockingOnThisMachine() const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: AutoApproveReporter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class AutoApproveReporter : public Reporter
|
|
{
|
|
public:
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: Path.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Path
|
|
{
|
|
std::string path_;
|
|
std::string separator_ = SystemUtils::getDirectorySeparator();
|
|
|
|
public:
|
|
Path(const std::string& start);
|
|
|
|
std::string toString() const;
|
|
std::string toString(const std::string& directoryPathSeparator) const;
|
|
|
|
Path operator+(const std::string& addition) const;
|
|
Path operator/(const std::string& addition) const;
|
|
Path operator/(const Path addition) const;
|
|
|
|
static std::string normalizeSeparators(const std::string& path);
|
|
std::string removeRedundantDirectorySeparators(std::string path) const;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalNamer.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalNamer
|
|
{
|
|
public:
|
|
virtual ~ApprovalNamer() = default;
|
|
virtual std::string getApprovedFile(std::string extensionWithDot) const = 0;
|
|
virtual std::string getReceivedFile(std::string extensionWithDot) const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalTestNamer.h
|
|
|
|
#include <vector>
|
|
#include <stdexcept>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class TestName
|
|
{
|
|
public:
|
|
const std::string& getFileName() const;
|
|
std::string getOriginalFileName();
|
|
|
|
void setFileName(const std::string& file);
|
|
|
|
private:
|
|
static void checkBuildConfiguration(const std::string& fileName);
|
|
|
|
public:
|
|
static std::string getMisconfiguredBuildHelp(const std::string& fileName);
|
|
static std::string checkParentDirectoriesForFile(const std::string& file);
|
|
static bool registerRootDirectoryFromMainFile(const std::string& file);
|
|
static std::string getRootDirectory();
|
|
|
|
std::vector<std::string> sections;
|
|
static std::string directoryPrefix;
|
|
static bool checkBuildConfig_;
|
|
|
|
private:
|
|
std::string handleBoostQuirks() const;
|
|
std::string findFileName(const std::string& file);
|
|
static std::string& rootDirectoryStorage();
|
|
|
|
std::string fileName;
|
|
std::string originalFileName;
|
|
};
|
|
|
|
class TestConfiguration
|
|
{
|
|
public:
|
|
std::string subdirectory;
|
|
};
|
|
|
|
class ApprovalTestNamer : public ApprovalNamer
|
|
{
|
|
private:
|
|
public:
|
|
ApprovalTestNamer() = default;
|
|
|
|
std::string getTestName() const;
|
|
|
|
static std::string convertToFileName(const std::string& fileName);
|
|
|
|
static TestName& getCurrentTest();
|
|
|
|
static std::string getMisconfiguredMainHelp();
|
|
|
|
// Deprecated - please use getSourceFileName
|
|
std::string getFileName() const;
|
|
|
|
std::string getSourceFileName() const;
|
|
|
|
std::string getTestSourceDirectory() const;
|
|
|
|
std::string getRelativeTestSourceDirectory() const;
|
|
|
|
std::string getApprovalsSubdirectory() const;
|
|
|
|
std::string getDirectory() const;
|
|
|
|
static TestName& currentTest(TestName* value = nullptr);
|
|
|
|
static TestConfiguration& testConfiguration();
|
|
|
|
virtual std::string getApprovedFile(std::string extensionWithDot) const override;
|
|
|
|
virtual std::string getReceivedFile(std::string extensionWithDot) const override;
|
|
|
|
std::string getOutputFileBaseName() const;
|
|
|
|
std::string getFullFileName(const std::string& approved,
|
|
const std::string& extensionWithDot) const;
|
|
|
|
static bool setCheckBuildConfig(bool enabled);
|
|
};
|
|
}
|
|
|
|
// ******************** From: SectionNameDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class APPROVAL_TESTS_NO_DISCARD SectionNameDisposer
|
|
{
|
|
public:
|
|
SectionNameDisposer(TestName& currentTest_, const std::string& scope_name);
|
|
SectionNameDisposer(const SectionNameDisposer&) = default;
|
|
|
|
~SectionNameDisposer();
|
|
|
|
private:
|
|
TestName& currentTest;
|
|
};
|
|
}
|
|
|
|
// ******************** From: NamerFactory.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
struct NamerFactory
|
|
{
|
|
static SectionNameDisposer appendToOutputFilename(const std::string& sectionName);
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalUtils.h
|
|
|
|
#include <iosfwd>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalUtils
|
|
{
|
|
public:
|
|
static void writeHeader(std::ostream& stream, const std::string& header);
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalComparator.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalComparator
|
|
{
|
|
public:
|
|
virtual ~ApprovalComparator() = default;
|
|
|
|
virtual bool contentsAreEquivalent(std::string receivedPath,
|
|
std::string approvedPath) const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ComparatorDisposer.h
|
|
|
|
#include <map>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
using ComparatorContainer =
|
|
std::map<std::string, std::shared_ptr<ApprovalComparator>>;
|
|
|
|
class APPROVAL_TESTS_NO_DISCARD ComparatorDisposer
|
|
{
|
|
public:
|
|
ComparatorDisposer(
|
|
ComparatorContainer& comparators_,
|
|
const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator_,
|
|
std::shared_ptr<ApprovalTests::ApprovalComparator> newComparator);
|
|
|
|
ComparatorDisposer(const ComparatorDisposer&) = delete;
|
|
|
|
ComparatorDisposer(ComparatorDisposer&& other) noexcept;
|
|
|
|
~ComparatorDisposer();
|
|
|
|
private:
|
|
// A disposer becomes inactive when it is moved from.
|
|
// This is done to prevent a comparator from being disposed twice.
|
|
bool isActive = true;
|
|
ComparatorContainer& comparators;
|
|
std::string ext_;
|
|
std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ComparatorFactory.h
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
class ComparatorFactory
|
|
{
|
|
private:
|
|
static ComparatorContainer& comparators();
|
|
|
|
public:
|
|
static ComparatorDisposer
|
|
registerComparator(const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalComparator> comparator);
|
|
|
|
static std::shared_ptr<ApprovalComparator>
|
|
getComparatorForFile(const std::string& receivedPath);
|
|
|
|
static std::shared_ptr<ApprovalComparator>
|
|
getComparatorForFileExtensionWithDot(const std::string& fileExtensionWithDot);
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalWriter.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalWriter
|
|
{
|
|
public:
|
|
virtual ~ApprovalWriter() = default;
|
|
virtual std::string getFileExtensionWithDot() const = 0;
|
|
virtual void write(std::string path) const = 0;
|
|
virtual void cleanUpReceived(std::string receivedPath) const = 0;
|
|
};
|
|
}
|
|
|
|
// ******************** From: StringWriter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class StringWriter : public ApprovalWriter
|
|
{
|
|
private:
|
|
std::string s;
|
|
std::string ext;
|
|
|
|
public:
|
|
explicit StringWriter(std::string contents,
|
|
std::string fileExtensionWithDot = ".txt");
|
|
|
|
std::string getFileExtensionWithDot() const override;
|
|
|
|
void write(std::string path) const override;
|
|
|
|
void Write(std::ostream& out) const;
|
|
|
|
virtual void cleanUpReceived(std::string receivedPath) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: FileApprover.h
|
|
|
|
|
|
#include <memory>
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class FileApprover
|
|
{
|
|
|
|
public:
|
|
FileApprover() = default;
|
|
|
|
~FileApprover() = default;
|
|
|
|
/*! \brief Register a custom comparater, which will be used to compare approved
|
|
* and received files with the given extension.
|
|
*
|
|
* @param extensionWithDot A file extention, such as ".jpg"
|
|
* @param comparator <tt>std::shared_ptr</tt> to a ApprovalTests::ApprovalComparator
|
|
* instance
|
|
* @return A "Disposable" object. The caller should hold on to this object.
|
|
* When it is destroyed, the customisation will be reversed.
|
|
*
|
|
* \see For more information, see
|
|
* \userguide{CustomComparators,Custom Comparators}
|
|
*/
|
|
static ComparatorDisposer
|
|
registerComparatorForExtension(const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalComparator> comparator);
|
|
|
|
//! This overload is an implementation detail. To add a new comparator, use registerComparatorForExtension().
|
|
static void verify(const std::string& receivedPath,
|
|
const std::string& approvedPath,
|
|
const ApprovalComparator& comparator);
|
|
|
|
static void verify(const std::string& receivedPath,
|
|
const std::string& approvedPath);
|
|
|
|
static void
|
|
verify(const ApprovalNamer& n, const ApprovalWriter& s, const Reporter& r);
|
|
|
|
static void reportAfterTryingFrontLoadedReporter(const std::string& receivedPath,
|
|
const std::string& approvedPath,
|
|
const Reporter& r);
|
|
|
|
using TestPassedNotification = std::function<void()>;
|
|
static void setTestPassedNotification(TestPassedNotification notification);
|
|
static void notifyTestPassed();
|
|
|
|
private:
|
|
static TestPassedNotification testPassedNotification_;
|
|
};
|
|
}
|
|
|
|
// ******************** From: FmtToString.h
|
|
#ifdef FMT_VERSION
|
|
namespace ApprovalTests
|
|
{
|
|
class FmtToString
|
|
{
|
|
public:
|
|
template <typename T> static std::string toString(const T& printable)
|
|
{
|
|
(void)printable;
|
|
return fmt::to_string(printable);
|
|
}
|
|
};
|
|
|
|
}
|
|
#endif
|
|
|
|
// ******************** From: FileNameSanitizerFactory.h
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
using FileNameSanitizer = std::function<std::string(std::string)>;
|
|
|
|
class FileNameSanitizerFactory
|
|
{
|
|
public:
|
|
static bool isForbidden(char c);
|
|
static std::string defaultSanitizer(std::string fileName);
|
|
static FileNameSanitizer currentSanitizer;
|
|
};
|
|
}
|
|
|
|
// ******************** From: FileNameSanitizerDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class APPROVAL_TESTS_NO_DISCARD FileNameSanitizerDisposer
|
|
{
|
|
private:
|
|
FileNameSanitizer previous_result;
|
|
|
|
public:
|
|
explicit FileNameSanitizerDisposer(FileNameSanitizer sanitizer);
|
|
FileNameSanitizerDisposer(const FileNameSanitizerDisposer&) = default;
|
|
|
|
~FileNameSanitizerDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: SubdirectoryDisposer.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useApprovalsSubdirectory()
|
|
class APPROVAL_TESTS_NO_DISCARD SubdirectoryDisposer
|
|
{
|
|
private:
|
|
std::string previous_result;
|
|
|
|
public:
|
|
explicit SubdirectoryDisposer(std::string subdirectory);
|
|
SubdirectoryDisposer(const SubdirectoryDisposer&) = default;
|
|
|
|
~SubdirectoryDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: DefaultReporterFactory.h
|
|
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useAsDefaultReporter()
|
|
class DefaultReporterFactory
|
|
{
|
|
private:
|
|
static std::shared_ptr<Reporter>& defaultReporter();
|
|
|
|
public:
|
|
static std::shared_ptr<Reporter> getDefaultReporter();
|
|
|
|
static void setDefaultReporter(const std::shared_ptr<Reporter>& reporter);
|
|
};
|
|
}
|
|
|
|
// ******************** From: DefaultReporterDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useAsDefaultReporter()
|
|
class APPROVAL_TESTS_NO_DISCARD DefaultReporterDisposer
|
|
{
|
|
private:
|
|
std::shared_ptr<Reporter> previous_result;
|
|
|
|
public:
|
|
explicit DefaultReporterDisposer(const std::shared_ptr<Reporter>& reporter);
|
|
|
|
~DefaultReporterDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: FirstWorkingReporter.h
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class FirstWorkingReporter : public Reporter
|
|
{
|
|
private:
|
|
std::vector<std::shared_ptr<Reporter>> reporters;
|
|
|
|
public:
|
|
// Note that FirstWorkingReporter takes ownership of the given Reporter objects
|
|
explicit FirstWorkingReporter(const std::vector<Reporter*>& theReporters);
|
|
|
|
explicit FirstWorkingReporter(
|
|
const std::vector<std::shared_ptr<Reporter>>& reporters_);
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: DefaultFrontLoadedReporter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class DefaultFrontLoadedReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
DefaultFrontLoadedReporter();
|
|
};
|
|
}
|
|
|
|
// ******************** From: FrontLoadedReporterFactory.h
|
|
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useAsFrontLoadedReporter()
|
|
class FrontLoadedReporterFactory
|
|
{
|
|
static std::shared_ptr<Reporter>& frontLoadedReporter();
|
|
|
|
public:
|
|
static std::shared_ptr<Reporter> getFrontLoadedReporter();
|
|
|
|
static void setFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter);
|
|
};
|
|
}
|
|
|
|
// ******************** From: FrontLoadedReporterDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useAsFrontLoadedReporter()
|
|
class APPROVAL_TESTS_NO_DISCARD FrontLoadedReporterDisposer
|
|
{
|
|
private:
|
|
std::shared_ptr<Reporter> previous_result;
|
|
|
|
public:
|
|
explicit FrontLoadedReporterDisposer(const std::shared_ptr<Reporter>& reporter);
|
|
FrontLoadedReporterDisposer(const FrontLoadedReporterDisposer&) = default;
|
|
|
|
~FrontLoadedReporterDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: Scrubbers.h
|
|
|
|
#include <string>
|
|
#include <functional>
|
|
#include <regex>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
using Scrubber = std::function<std::string(const std::string&)>;
|
|
namespace Scrubbers
|
|
{
|
|
std::string doNothing(const std::string& input);
|
|
|
|
/**@name Regex-based scrubbers
|
|
|
|
See \userguide{how_tos/ScrubNonDeterministicOutput,regular-expressions-regex,Regular Expressions (regex)}
|
|
*/
|
|
///@{
|
|
using RegexMatch = std::sub_match<std::string::const_iterator>;
|
|
using RegexReplacer = std::function<std::string(const RegexMatch&)>;
|
|
|
|
std::string scrubRegex(const std::string& input,
|
|
const std::regex& regex,
|
|
const RegexReplacer& replaceFunction);
|
|
|
|
Scrubber createRegexScrubber(const std::regex& regexPattern,
|
|
const RegexReplacer& replacer);
|
|
|
|
Scrubber createRegexScrubber(const std::regex& regexPattern,
|
|
const std::string& replacementText);
|
|
|
|
Scrubber createRegexScrubber(const std::string& regexString,
|
|
const std::string& replacementText);
|
|
///@}
|
|
|
|
std::string scrubGuid(const std::string& input);
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultNamerFactory.h
|
|
|
|
|
|
#include <memory>
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
using NamerCreator = std::function<std::shared_ptr<ApprovalNamer>()>;
|
|
|
|
//! Implementation detail of Approvals::useAsDefaultNamer()
|
|
class DefaultNamerFactory
|
|
{
|
|
private:
|
|
static NamerCreator& defaultNamer();
|
|
|
|
public:
|
|
static NamerCreator getDefaultNamer();
|
|
|
|
static void setDefaultNamer(NamerCreator namer);
|
|
};
|
|
}
|
|
|
|
// ******************** From: Options.h
|
|
|
|
#include <utility>
|
|
#include <exception>
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Options
|
|
{
|
|
public:
|
|
class FileOptions
|
|
{
|
|
const Options* options_ = nullptr; // set in Options::fileOptions()
|
|
std::string fileExtensionWithDot_ = ".txt";
|
|
friend class Options;
|
|
|
|
FileOptions() = default;
|
|
|
|
explicit FileOptions(std::string fileExtensionWithDot);
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
FileOptions clone() const;
|
|
|
|
public:
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
const std::string& getFileExtension() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Options withFileExtension(const std::string& fileExtensionWithDot) const;
|
|
};
|
|
|
|
private:
|
|
FileOptions fileOptions_;
|
|
Scrubber scrubber_ = Scrubbers::doNothing;
|
|
const Reporter& reporter_ = defaultReporter();
|
|
std::shared_ptr<ApprovalNamer> namer_ = DefaultNamerFactory::getDefaultNamer()();
|
|
bool usingDefaultScrubber_ = true;
|
|
|
|
Options(FileOptions fileOptions,
|
|
Scrubber scrubber,
|
|
const Reporter& reporter,
|
|
bool usingDefaultScrubber,
|
|
std::shared_ptr<ApprovalNamer> namer);
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Options clone(const FileOptions& fileOptions) const;
|
|
|
|
static const Reporter& defaultReporter();
|
|
|
|
public:
|
|
Options() = default;
|
|
|
|
explicit Options(Scrubber scrubber);
|
|
|
|
explicit Options(const Reporter& reporter);
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
FileOptions fileOptions() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Scrubber getScrubber() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
bool isUsingDefaultScrubber() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string scrub(const std::string& input) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
const Reporter& getReporter() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Options withReporter(const Reporter& reporter) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Options withScrubber(Scrubber scrubber) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::shared_ptr<ApprovalNamer> getNamer() const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Options withNamer(std::shared_ptr<ApprovalNamer> namer);
|
|
};
|
|
|
|
namespace Detail
|
|
{
|
|
//! Helper to prevent compilation failure when types are wrongly treated as Option
|
|
// or Reporter:
|
|
template <typename T, typename R = void>
|
|
using EnableIfNotOptionsOrReporter = typename std::enable_if<
|
|
(!std::is_same<Options, typename std::decay<T>::type>::value) &&
|
|
(!std::is_base_of<Reporter, typename std::decay<T>::type>::value),
|
|
R>::type;
|
|
|
|
//! Helper to prevent compilation failure when types are wrongly treated as Option,
|
|
// Reporter or String:
|
|
template <typename T, typename R = void>
|
|
using EnableIfNotOptionsOrReporterOrString = typename std::enable_if<
|
|
(!std::is_same<Options, typename std::decay<T>::type>::value) &&
|
|
(!std::is_same<std::string, typename std::decay<T>::type>::value) &&
|
|
(!std::is_same<char*, typename std::decay<T>::type>::value) &&
|
|
(!std::is_same<const char*, typename std::decay<T>::type>::value) &&
|
|
(!std::is_base_of<Reporter, typename std::decay<T>::type>::value),
|
|
R>::type;
|
|
} // namespace Detail
|
|
}
|
|
|
|
// ******************** From: ExistingFileNamer.h
|
|
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ExistingFileNamer : public ApprovalNamer
|
|
{
|
|
std::string filePath;
|
|
const Options& options_;
|
|
|
|
public:
|
|
explicit ExistingFileNamer(std::string filePath_, const Options& options);
|
|
|
|
ExistingFileNamer(const ExistingFileNamer& x);
|
|
|
|
ExistingFileNamer(ExistingFileNamer&& x) noexcept;
|
|
|
|
virtual std::string getApprovedFile(std::string extensionWithDot) const override;
|
|
|
|
virtual std::string
|
|
getReceivedFile(std::string /*extensionWithDot*/) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ExistingFile.h
|
|
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ExistingFile : public ApprovalWriter
|
|
{
|
|
std::string filePath;
|
|
bool deleteScrubbedFile = false;
|
|
const Options& options_;
|
|
|
|
std::string scrub(std::string fileName, const Options& options);
|
|
|
|
public:
|
|
explicit ExistingFile(std::string filePath_, const Options& options);
|
|
virtual std::string getFileExtensionWithDot() const override;
|
|
virtual void write(std::string /*path*/) const override;
|
|
virtual void cleanUpReceived(std::string receivedPath) const override;
|
|
ExistingFileNamer getNamer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: DefaultNamerDisposer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
//! Implementation detail of Approvals::useAsDefaultNamer()
|
|
class APPROVAL_TESTS_NO_DISCARD DefaultNamerDisposer
|
|
{
|
|
private:
|
|
NamerCreator previous_result;
|
|
|
|
public:
|
|
explicit DefaultNamerDisposer(NamerCreator namerCreator);
|
|
DefaultNamerDisposer(const DefaultNamerDisposer&) = default;
|
|
|
|
~DefaultNamerDisposer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: Approvals.h
|
|
#include <string>
|
|
#include <functional>
|
|
#include <exception>
|
|
#include <utility>
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
// TCompileTimeOptions must have a type ToStringConverter, which must have a method toString()
|
|
template <typename TCompileTimeOptions> class TApprovals
|
|
{
|
|
private:
|
|
TApprovals() = default;
|
|
|
|
~TApprovals() = default;
|
|
|
|
public:
|
|
static std::shared_ptr<ApprovalNamer> getDefaultNamer()
|
|
{
|
|
return DefaultNamerFactory::getDefaultNamer()();
|
|
}
|
|
|
|
template <typename T>
|
|
using IsNotDerivedFromWriter =
|
|
typename std::enable_if<!std::is_base_of<ApprovalWriter, T>::value,
|
|
int>::type;
|
|
|
|
/**@name Verifying single objects
|
|
|
|
See \userguide{TestingSingleObjects,Testing Single Objects}
|
|
*/
|
|
///@{
|
|
static void verify(const std::string& contents,
|
|
const Options& options = Options())
|
|
{
|
|
StringWriter writer(options.scrub(contents),
|
|
options.fileOptions().getFileExtension());
|
|
FileApprover::verify(*options.getNamer(), writer, options.getReporter());
|
|
}
|
|
|
|
template <typename T, typename = IsNotDerivedFromWriter<T>>
|
|
static void verify(const T& contents, const Options& options = Options())
|
|
{
|
|
verify(TCompileTimeOptions::ToStringConverter::toString(contents), options);
|
|
}
|
|
|
|
template <typename T,
|
|
typename Function,
|
|
typename = Detail::EnableIfNotOptionsOrReporter<Function>>
|
|
static void
|
|
verify(const T& contents, Function converter, const Options& options = Options())
|
|
{
|
|
std::stringstream s;
|
|
converter(contents, s);
|
|
verify(s.str(), options);
|
|
}
|
|
|
|
/// Note that this overload ignores any scrubber in options
|
|
static void verify(const ApprovalWriter& writer,
|
|
const Options& options = Options())
|
|
{
|
|
FileApprover::verify(*options.getNamer(), writer, options.getReporter());
|
|
}
|
|
///@}
|
|
|
|
/**@name Verifying containers of objects - supplying an iterator range
|
|
|
|
See \userguide{TestingContainers,Testing Containers}
|
|
*/
|
|
///@{
|
|
template <typename Iterator>
|
|
static void
|
|
verifyAll(const std::string& header,
|
|
const Iterator& start,
|
|
const Iterator& finish,
|
|
std::function<void(decltype(*start), std::ostream&)> converter,
|
|
const Options& options = Options())
|
|
{
|
|
std::stringstream s;
|
|
ApprovalUtils::writeHeader(s, header);
|
|
for (auto it = start; it != finish; ++it)
|
|
{
|
|
converter(*it, s);
|
|
s << '\n';
|
|
}
|
|
verify(s.str(), options);
|
|
}
|
|
///@}
|
|
|
|
/**@name Verifying containers of objects - supplying a container
|
|
|
|
See \userguide{TestingContainers,Testing Containers}
|
|
*/
|
|
///@{
|
|
template <typename Container>
|
|
static void verifyAll(
|
|
const std::string& header,
|
|
const Container& list,
|
|
std::function<void(typename Container::value_type, std::ostream&)> converter,
|
|
const Options& options = Options())
|
|
{
|
|
verifyAll<typename Container::const_iterator>(
|
|
header, list.begin(), list.end(), converter, options);
|
|
}
|
|
|
|
template <typename Container>
|
|
static void verifyAll(const std::string& header,
|
|
const Container& list,
|
|
const Options& options = Options())
|
|
{
|
|
int i = 0;
|
|
verifyAll<Container>(
|
|
header,
|
|
list,
|
|
[&](typename Container::value_type e, std::ostream& s) {
|
|
s << "[" << i++
|
|
<< "] = " << TCompileTimeOptions::ToStringConverter::toString(e);
|
|
},
|
|
options);
|
|
}
|
|
|
|
template <typename Container>
|
|
static void verifyAll(const Container& list, const Options& options = Options())
|
|
{
|
|
verifyAll<Container>("", list, options);
|
|
}
|
|
///@}
|
|
|
|
/**@name Verifying containers of objects - supplying an initializer list
|
|
|
|
See \userguide{TestingContainers,Testing Containers}
|
|
*/
|
|
///@{
|
|
template <typename T>
|
|
static void
|
|
verifyAll(const std::string& header,
|
|
const std::initializer_list<T>& list,
|
|
std::function<void(typename std::initializer_list<T>::value_type,
|
|
std::ostream&)> converter,
|
|
const Options& options = Options())
|
|
{
|
|
verifyAll<std::initializer_list<T>>(header, list, converter, options);
|
|
}
|
|
|
|
template <typename T>
|
|
static void verifyAll(const std::string& header,
|
|
const std::initializer_list<T>& list,
|
|
const Options& options = Options())
|
|
{
|
|
verifyAll<std::initializer_list<T>>(header, list, options);
|
|
}
|
|
|
|
template <typename T>
|
|
static void verifyAll(const std::initializer_list<T>& list,
|
|
const Options& options = Options())
|
|
{
|
|
verifyAll<std::initializer_list<T>>("", list, options);
|
|
}
|
|
///@}
|
|
|
|
/**@name Other verify methods
|
|
*/
|
|
///@{
|
|
|
|
/*! \brief Verify the text of an exception
|
|
|
|
See \userguide{TestingExceptions,testing-exception-messages,Testing exception messages}
|
|
*/
|
|
static void
|
|
verifyExceptionMessage(const std::function<void(void)>& functionThatThrows,
|
|
const Options& options = Options())
|
|
{
|
|
std::string message = "*** no exception thrown ***";
|
|
try
|
|
{
|
|
functionThatThrows();
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
message = e.what();
|
|
}
|
|
verify(message, options);
|
|
}
|
|
|
|
/// Verify an existing file, that has already been written out
|
|
static void verifyExistingFile(const std::string& filePath,
|
|
const Options& options = Options())
|
|
{
|
|
ExistingFile writer(filePath, options);
|
|
FileApprover::verify(writer.getNamer(), writer, options.getReporter());
|
|
}
|
|
///@}
|
|
|
|
/**@name Customising Approval Tests
|
|
|
|
These static methods customise various aspects
|
|
of Approval Tests behaviour.
|
|
*/
|
|
///@{
|
|
|
|
/// See \userguide{Configuration,using-sub-directories-for-approved-files,Using sub-directories for approved files}
|
|
static SubdirectoryDisposer
|
|
useApprovalsSubdirectory(const std::string& subdirectory = "approval_tests")
|
|
{
|
|
return SubdirectoryDisposer(subdirectory);
|
|
}
|
|
|
|
/// See \userguide{Reporters,registering-a-default-reporter,Registering a default reporter}
|
|
static DefaultReporterDisposer
|
|
useAsDefaultReporter(const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
return DefaultReporterDisposer(reporter);
|
|
}
|
|
|
|
/// See \userguide{Reporters,front-loaded-reporters,Front Loaded Reporters}
|
|
static FrontLoadedReporterDisposer
|
|
useAsFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
return FrontLoadedReporterDisposer(reporter);
|
|
}
|
|
|
|
/// See \userguide{Namers,registering-a-custom-namer,Registering a Custom Namer}
|
|
static DefaultNamerDisposer useAsDefaultNamer(NamerCreator namerCreator)
|
|
{
|
|
return DefaultNamerDisposer(std::move(namerCreator));
|
|
}
|
|
|
|
/// See \userguide{Namers,converting-test-names-to-valid-filenames,Converting Test Names to Valid FileNames}
|
|
static FileNameSanitizerDisposer useFileNameSanitizer(FileNameSanitizer sanitizer)
|
|
{
|
|
return FileNameSanitizerDisposer(sanitizer);
|
|
}
|
|
///@}
|
|
};
|
|
|
|
#ifndef APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER
|
|
#define APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER StringMaker
|
|
#endif
|
|
|
|
// Warning: Do not use CompileTimeOptions directly.
|
|
// This interface is subject to change, as future
|
|
// compile-time options are added.
|
|
template <typename TToString> struct CompileTimeOptions
|
|
{
|
|
using ToStringConverter = TToString;
|
|
// more template types may be added to CompileTimeOptions in future, if we add
|
|
// more flexibility that requires compile-time configuration.
|
|
};
|
|
|
|
// Template parameter TToString must have a method toString()
|
|
// This interface will not change, as future compile-time options are added.
|
|
template <typename TToString>
|
|
struct ToStringCompileTimeOptions : CompileTimeOptions<TToString>
|
|
{
|
|
};
|
|
|
|
using Approvals =
|
|
TApprovals<ToStringCompileTimeOptions<APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER>>;
|
|
}
|
|
|
|
// ******************** From: TemplatedCustomNamer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class TemplatedCustomNamer : public ApprovalTests::ApprovalNamer
|
|
{
|
|
private:
|
|
ApprovalTests::ApprovalTestNamer namer_;
|
|
std::string template_;
|
|
|
|
public:
|
|
explicit TemplatedCustomNamer(std::string templateString);
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Path constructFromTemplate(const std::string& extensionWithDot,
|
|
const std::string& approvedOrReceived) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string getApprovedFile(std::string extensionWithDot) const override;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string getReceivedFile(std::string extensionWithDot) const override;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Path getApprovedFileAsPath(std::string extensionWithDot) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
Path getReceivedFileAsPath(std::string extensionWithDot) const;
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
static std::shared_ptr<TemplatedCustomNamer> create(std::string templateString);
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
static DefaultNamerDisposer useAsDefaultNamer(std::string templateString);
|
|
};
|
|
}
|
|
|
|
// ******************** From: GoogleCustomizationsFactory.h
|
|
|
|
|
|
#include <vector>
|
|
#include <functional>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class GoogleCustomizationsFactory
|
|
{
|
|
public:
|
|
using Comparator = std::function<bool(const std::string&, const std::string&)>;
|
|
|
|
private:
|
|
using ComparatorContainer = std::vector<Comparator>;
|
|
static ComparatorContainer& comparatorContainer();
|
|
|
|
public:
|
|
static ComparatorContainer getEquivalencyChecks();
|
|
|
|
APPROVAL_TESTS_NO_DISCARD static bool
|
|
addTestCaseNameRedundancyCheck(const Comparator& comparator);
|
|
};
|
|
}
|
|
|
|
// ******************** From: MoreHelpMessages.h
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class MoreHelpMessages
|
|
{
|
|
public:
|
|
static void deprecatedFunctionCalled(const std::string& message,
|
|
const std::string& file,
|
|
int lineNumber);
|
|
};
|
|
}
|
|
|
|
// ******************** From: CartesianProduct.h
|
|
|
|
#include <functional>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace CartesianProduct
|
|
{
|
|
namespace Detail
|
|
{
|
|
|
|
// C++14 compatibility
|
|
// See https://en.cppreference.com/w/cpp/types/enable_if
|
|
template <bool B, class T = void>
|
|
using enable_if_t = typename std::enable_if<B, T>::type;
|
|
|
|
// See https://en.cppreference.com/w/cpp/utility/integer_sequence
|
|
template <std::size_t... Is> struct index_sequence
|
|
{
|
|
};
|
|
|
|
template <std::size_t N, std::size_t... Is>
|
|
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Is...>
|
|
{
|
|
};
|
|
|
|
template <std::size_t... Is>
|
|
struct make_index_sequence<0, Is...> : index_sequence<Is...>
|
|
{
|
|
};
|
|
// End of C++14 compatibility
|
|
|
|
// Return the size of a tuple - constexpr for use as a template argument
|
|
// See https://en.cppreference.com/w/cpp/utility/tuple/tuple_size
|
|
template <class Tuple> constexpr std::size_t tuple_size()
|
|
{
|
|
return std::tuple_size<
|
|
typename std::remove_reference<Tuple>::type>::value;
|
|
}
|
|
|
|
template <class Tuple>
|
|
using make_tuple_idxs = make_index_sequence<tuple_size<Tuple>()>;
|
|
|
|
// C++17 compatibility
|
|
// See https://en.cppreference.com/w/cpp/utility/apply
|
|
template <class F, class Tuple, std::size_t... I>
|
|
constexpr auto apply_impl(F&& f, Tuple&& t, index_sequence<I...>)
|
|
-> decltype(std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...))
|
|
{
|
|
return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(t))...);
|
|
}
|
|
|
|
template <class F, class Tuple>
|
|
auto apply(F&& f, Tuple&& t) -> decltype(apply_impl(std::forward<F>(f),
|
|
std::forward<Tuple>(t),
|
|
make_tuple_idxs<Tuple>{}))
|
|
{
|
|
return apply_impl(
|
|
std::forward<F>(f), std::forward<Tuple>(t), make_tuple_idxs<Tuple>{});
|
|
}
|
|
// End of C++17 compatibility
|
|
|
|
template <class Tuple, class F, std::size_t... Is>
|
|
void for_each_impl(Tuple&& t, F&& f, index_sequence<Is...>)
|
|
{
|
|
(void)std::initializer_list<int>{
|
|
(std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t))), 0)...};
|
|
}
|
|
|
|
template <class Tuple, class F> void for_each(Tuple&& t, F&& f)
|
|
{
|
|
for_each_impl(
|
|
std::forward<Tuple>(t), std::forward<F>(f), make_tuple_idxs<Tuple>{});
|
|
}
|
|
|
|
template <class Tuple, class F, std::size_t... Is>
|
|
auto transform_impl(Tuple&& t, F&& f, index_sequence<Is...>)
|
|
-> decltype(std::make_tuple(
|
|
std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t)))...))
|
|
{
|
|
return std::make_tuple(
|
|
std::forward<F>(f)(std::get<Is>(std::forward<Tuple>(t)))...);
|
|
}
|
|
|
|
template <class F, class Tuple>
|
|
auto transform(Tuple&& t, F&& f = {})
|
|
-> decltype(transform_impl(std::forward<Tuple>(t),
|
|
std::forward<F>(f),
|
|
make_tuple_idxs<Tuple>{}))
|
|
{
|
|
return transform_impl(
|
|
std::forward<Tuple>(t), std::forward<F>(f), make_tuple_idxs<Tuple>{});
|
|
}
|
|
|
|
template <class Predicate> struct find_if_body
|
|
{
|
|
const Predicate& pred;
|
|
std::size_t& index;
|
|
std::size_t currentIndex = 0;
|
|
bool found = false;
|
|
|
|
find_if_body(const Predicate& p, std::size_t& i) : pred(p), index(i)
|
|
{
|
|
}
|
|
|
|
template <typename T> void operator()(T&& value)
|
|
{
|
|
if (found)
|
|
return;
|
|
if (pred(std::forward<T>(value)))
|
|
{
|
|
index = currentIndex;
|
|
found = true;
|
|
}
|
|
++currentIndex;
|
|
}
|
|
};
|
|
|
|
template <class Predicate, class Tuple>
|
|
std::size_t find_if(Tuple&& tuple, Predicate pred = {})
|
|
{
|
|
std::size_t idx = tuple_size<Tuple>();
|
|
for_each(std::forward<Tuple>(tuple), find_if_body<Predicate>(pred, idx));
|
|
return idx;
|
|
}
|
|
|
|
template <class Predicate, class Tuple>
|
|
bool any_of(Tuple&& tuple, Predicate pred = {})
|
|
{
|
|
return find_if(std::forward<Tuple>(tuple), pred) != tuple_size<Tuple>();
|
|
}
|
|
|
|
struct is_range_empty
|
|
{
|
|
template <class T> bool operator()(const T& range) const
|
|
{
|
|
using std::begin;
|
|
using std::end;
|
|
return begin(range) == end(range);
|
|
}
|
|
};
|
|
|
|
// Transform an iterator into a value reference which will then be passed to the visitor function:
|
|
struct dereference_iterator
|
|
{
|
|
template <class It>
|
|
auto operator()(It&& it) const -> decltype(*std::forward<It>(it))
|
|
{
|
|
return *std::forward<It>(it);
|
|
}
|
|
};
|
|
|
|
// Increment outermost iterator. If it reaches its end, we're finished and do nothing.
|
|
template <class Its, std::size_t I = tuple_size<Its>() - 1>
|
|
enable_if_t<I == 0> increment_iterator(Its& it, const Its&, const Its&)
|
|
{
|
|
++std::get<I>(it);
|
|
}
|
|
|
|
// Increment inner iterator. If it reaches its end, we reset it and increment the previous iterator.
|
|
template <class Its, std::size_t I = tuple_size<Its>() - 1>
|
|
enable_if_t<I != 0>
|
|
increment_iterator(Its& its, const Its& begins, const Its& ends)
|
|
{
|
|
if (++std::get<I>(its) == std::get<I>(ends))
|
|
{
|
|
std::get<I>(its) = std::get<I>(begins);
|
|
increment_iterator<Its, I - 1>(its, begins, ends);
|
|
}
|
|
}
|
|
} // namespace Detail
|
|
|
|
// This is what actually loops over all the containers, one element at a time
|
|
// It is called with a template type F that writes the inputs, and runs the converter, which writes the result(s)
|
|
// all for one set of container values - when called by verifyAllCombinations()
|
|
// More generally, F must have an operator() that acts on one set of input values.
|
|
template <class F, class... Ranges>
|
|
void cartesian_product(F&& f, const Ranges&... ranges)
|
|
{
|
|
using std::begin;
|
|
using std::end;
|
|
|
|
if (Detail::any_of<Detail::is_range_empty>(std::forward_as_tuple(ranges...)))
|
|
return;
|
|
|
|
const auto begins = std::make_tuple(begin(ranges)...);
|
|
const auto ends = std::make_tuple(end(ranges)...);
|
|
|
|
for (auto its = begins; std::get<0>(its) != std::get<0>(ends);
|
|
Detail::increment_iterator(its, begins, ends))
|
|
{
|
|
// Command-clicking on transform in CLion 2019.2.1 hangs with CLion with high CPU
|
|
// 'Use clang tidy' is turned off.
|
|
// Power-save turned on.
|
|
// Mac
|
|
Detail::apply(std::forward<F>(f),
|
|
Detail::transform<Detail::dereference_iterator>(its));
|
|
}
|
|
}
|
|
} // namespace CartesianProduct
|
|
} // namespace ApprovalTests
|
|
|
|
// ******************** From: DefaultReporter.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class DefaultReporter : public Reporter
|
|
{
|
|
public:
|
|
virtual bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: CombinationApprovals.h
|
|
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
template <typename TCompileTimeOptions> class TCombinationApprovals
|
|
{
|
|
private:
|
|
// Write out second or subsequent input value, with preceding comma and space
|
|
struct print_input
|
|
{
|
|
std::ostream& out;
|
|
template <class T> void operator()(const T& input)
|
|
{
|
|
out << ", " << TCompileTimeOptions::ToStringConverter::toString(input);
|
|
}
|
|
};
|
|
|
|
// Write out one row of output
|
|
template <class Converter> struct serialize
|
|
{
|
|
std::ostream& out;
|
|
Converter converter;
|
|
template <class T, class... Ts> void operator()(T&& input1_, Ts&&... inputs)
|
|
{
|
|
// First value is printed without trailing comma
|
|
out << "(" << TCompileTimeOptions::ToStringConverter::toString(input1_);
|
|
// Remaining values are printed with prefix of a comma
|
|
CartesianProduct::Detail::for_each(std::forward_as_tuple(inputs...),
|
|
print_input{out});
|
|
out << ") => " << converter(input1_, inputs...) << '\n';
|
|
}
|
|
};
|
|
|
|
public:
|
|
/**@name Verifying combinations of objects
|
|
|
|
See \userguide{TestingCombinations,Testing combinations}
|
|
*/
|
|
///@{
|
|
template <class Converter, class Container, class... Containers>
|
|
static void verifyAllCombinations(const Options& options,
|
|
const std::string& header,
|
|
Converter&& converter,
|
|
const Container& input0,
|
|
const Containers&... inputs)
|
|
{
|
|
std::stringstream s;
|
|
ApprovalUtils::writeHeader(s, header);
|
|
CartesianProduct::cartesian_product(
|
|
serialize<Converter>{s, std::forward<Converter>(converter)},
|
|
input0,
|
|
inputs...);
|
|
Approvals::verify(s.str(), options);
|
|
}
|
|
|
|
template <class Converter, class... Containers>
|
|
ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString<
|
|
Converter> static verifyAllCombinations(const std::string& header,
|
|
Converter&& converter,
|
|
const Containers&... inputs)
|
|
{
|
|
verifyAllCombinations(
|
|
Options(), header, std::forward<Converter>(converter), inputs...);
|
|
}
|
|
|
|
template <class Converter, class... Containers>
|
|
ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString<
|
|
Converter> static verifyAllCombinations(const Options& options,
|
|
Converter&& converter,
|
|
const Containers&... inputs)
|
|
{
|
|
verifyAllCombinations(
|
|
options, std::string(), std::forward<Converter>(converter), inputs...);
|
|
}
|
|
|
|
template <class Converter, class... Containers>
|
|
ApprovalTests::Detail::EnableIfNotOptionsOrReporterOrString<
|
|
Converter> static verifyAllCombinations(Converter&& converter,
|
|
const Containers&... inputs)
|
|
{
|
|
verifyAllCombinations(
|
|
Options(), std::string(), std::forward<Converter>(converter), inputs...);
|
|
}
|
|
///@}
|
|
};
|
|
|
|
using CombinationApprovals = TCombinationApprovals<
|
|
ToStringCompileTimeOptions<APPROVAL_TESTS_DEFAULT_STREAM_CONVERTER>>;
|
|
|
|
} // namespace ApprovalTests
|
|
|
|
// ******************** From: Storyboard.h
|
|
|
|
#include <iosfwd>
|
|
#include <sstream>
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Storyboard
|
|
{
|
|
private:
|
|
std::stringstream output_;
|
|
int frameCount_ = 0;
|
|
bool addNewLineBeforeNextFrame_ = false;
|
|
|
|
public:
|
|
Storyboard& addDescription(const std::string& description);
|
|
|
|
Storyboard& addDescriptionWithData(const std::string& description,
|
|
const std::string& data);
|
|
|
|
Storyboard& addFrame(const std::string& frame);
|
|
|
|
Storyboard& addFrame(const std::string& title, const std::string& frame);
|
|
|
|
Storyboard& addFrames(int numberOfFrames,
|
|
const std::function<std::string(int)>& function);
|
|
|
|
friend std::ostream& operator<<(std::ostream& os, const Storyboard& board);
|
|
};
|
|
}
|
|
|
|
// ******************** From: TextFileComparator.h
|
|
|
|
|
|
#include <fstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class TextFileComparator : public ApprovalComparator
|
|
{
|
|
public:
|
|
static std::ifstream::int_type getNextRelevantCharacter(std::ifstream& astream);
|
|
|
|
virtual bool contentsAreEquivalent(std::string receivedPath,
|
|
std::string approvedPath) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: ApprovalException.h
|
|
|
|
#include <exception>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalException : public std::exception
|
|
{
|
|
private:
|
|
std::string message;
|
|
|
|
public:
|
|
explicit ApprovalException(const std::string& msg);
|
|
|
|
virtual const char* what() const noexcept override;
|
|
};
|
|
|
|
class ApprovalMismatchException : public ApprovalException
|
|
{
|
|
private:
|
|
std::string format(const std::string& received, const std::string& approved);
|
|
|
|
public:
|
|
ApprovalMismatchException(const std::string& received,
|
|
const std::string& approved);
|
|
};
|
|
|
|
class ApprovalMissingException : public ApprovalException
|
|
{
|
|
private:
|
|
std::string format(const std::string& file);
|
|
|
|
public:
|
|
ApprovalMissingException(const std::string& /*received*/,
|
|
const std::string& approved);
|
|
};
|
|
}
|
|
|
|
// ******************** From: FrameworkIntegrations.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class FrameworkIntegrations
|
|
{
|
|
public:
|
|
static void
|
|
setTestPassedNotification(FileApprover::TestPassedNotification notification);
|
|
|
|
static void setCurrentTest(ApprovalTests::TestName* currentTest);
|
|
};
|
|
}
|
|
|
|
// ******************** From: BoostTestApprovals.h
|
|
|
|
|
|
#ifdef APPROVALS_BOOSTTEST
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
class BoostApprovalListener : public boost::unit_test::test_observer
|
|
{
|
|
ApprovalTests::TestName currentTest;
|
|
|
|
void test_unit_start(boost::unit_test::test_unit const& test) override
|
|
{
|
|
std::string path(test.p_file_name.begin(), test.p_file_name.end());
|
|
currentTest.setFileName(path);
|
|
|
|
currentTest.sections.push_back(test.p_name);
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { BOOST_CHECK(true); });
|
|
}
|
|
|
|
void test_unit_finish(boost::unit_test::test_unit const& /*test*/,
|
|
unsigned long) override
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
};
|
|
|
|
int register_our_listener(BoostApprovalListener& t)
|
|
{
|
|
boost::unit_test::framework::register_observer(t);
|
|
return 1;
|
|
}
|
|
|
|
BoostApprovalListener o;
|
|
auto dummy_variable = register_our_listener(o);
|
|
}
|
|
|
|
#endif // APPROVALS_BOOSTTEST
|
|
|
|
// ******************** From: Catch2Approvals.h
|
|
|
|
|
|
|
|
#if defined(APPROVALS_CATCH_EXISTING_MAIN)
|
|
#define APPROVALS_CATCH
|
|
#define CATCH_CONFIG_RUNNER
|
|
#elif defined(APPROVALS_CATCH)
|
|
#define CATCH_CONFIG_MAIN
|
|
#endif
|
|
|
|
#ifdef APPROVALS_CATCH
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#include <catch2/catch.hpp>
|
|
|
|
//namespace ApprovalTests {
|
|
struct Catch2ApprovalListener : Catch::TestEventListenerBase
|
|
{
|
|
ApprovalTests::TestName currentTest;
|
|
using TestEventListenerBase::
|
|
TestEventListenerBase; // This using allows us to use all base-class constructors
|
|
virtual void testCaseStarting(Catch::TestCaseInfo const& testInfo) override
|
|
{
|
|
|
|
currentTest.setFileName(testInfo.lineInfo.file);
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { REQUIRE(true); });
|
|
}
|
|
|
|
virtual void testCaseEnded(Catch::TestCaseStats const& /*testCaseStats*/) override
|
|
{
|
|
while (!currentTest.sections.empty())
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
}
|
|
|
|
virtual void sectionStarting(Catch::SectionInfo const& sectionInfo) override
|
|
{
|
|
currentTest.sections.push_back(sectionInfo.name);
|
|
}
|
|
|
|
virtual void sectionEnded(Catch::SectionStats const& /*sectionStats*/) override
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
};
|
|
//}
|
|
CATCH_REGISTER_LISTENER(Catch2ApprovalListener)
|
|
|
|
#endif
|
|
#ifdef TEST_COMMIT_REVERT_CATCH
|
|
|
|
//namespace ApprovalTests {
|
|
struct Catch2TestCommitRevert : Catch::TestEventListenerBase
|
|
{
|
|
using TestEventListenerBase::
|
|
TestEventListenerBase; // This using allows us to use all base-class constructors
|
|
virtual void testRunEnded(Catch::TestRunStats const& testRunStats) override
|
|
{
|
|
bool commit = testRunStats.totals.testCases.allOk();
|
|
std::string message = "r ";
|
|
if (commit)
|
|
{
|
|
std::cout << "git add -A \n";
|
|
std::cout << "git commit -m " << message;
|
|
}
|
|
else
|
|
{
|
|
std::cout << "git clean -fd \n";
|
|
std::cout << "git reset --hard HEAD \n";
|
|
}
|
|
}
|
|
};
|
|
//}
|
|
CATCH_REGISTER_LISTENER(Catch2TestCommitRevert)
|
|
#endif
|
|
|
|
// ******************** From: Catch2v3Approvals.h
|
|
|
|
|
|
#ifdef APPROVALS_CATCH2_V3
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#include <catch2/reporters/catch_reporter_event_listener.hpp>
|
|
#include <catch2/catch_test_macros.hpp>
|
|
#include <catch2/reporters/catch_reporter_registrars.hpp>
|
|
#include <catch2/catch_test_case_info.hpp>
|
|
|
|
//namespace ApprovalTests {
|
|
struct Catch2ApprovalListener : Catch::EventListenerBase
|
|
{
|
|
ApprovalTests::TestName currentTest;
|
|
using EventListenerBase::
|
|
EventListenerBase; // This using allows us to use all base-class constructors
|
|
virtual void testCaseStarting(Catch::TestCaseInfo const& testInfo) override
|
|
{
|
|
|
|
currentTest.setFileName(testInfo.lineInfo.file);
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { REQUIRE(true); });
|
|
}
|
|
|
|
virtual void testCaseEnded(Catch::TestCaseStats const& /*testCaseStats*/) override
|
|
{
|
|
while (!currentTest.sections.empty())
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
}
|
|
|
|
virtual void sectionStarting(Catch::SectionInfo const& sectionInfo) override
|
|
{
|
|
currentTest.sections.push_back(sectionInfo.name);
|
|
}
|
|
|
|
virtual void sectionEnded(Catch::SectionStats const& /*sectionStats*/) override
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
};
|
|
//}
|
|
CATCH_REGISTER_LISTENER(Catch2ApprovalListener)
|
|
|
|
#endif
|
|
|
|
// ******************** From: CppUTestApprovals.h
|
|
|
|
|
|
#ifdef APPROVALS_CPPUTEST_EXISTING_MAIN
|
|
#define APPROVALS_CPPUTEST
|
|
#endif
|
|
|
|
#ifdef APPROVALS_CPPUTEST
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#include <CppUTest/CommandLineTestRunner.h>
|
|
#include <CppUTest/TestPlugin.h>
|
|
#include <CppUTest/TestRegistry.h>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ApprovalTestsCppUTestPlugin : public TestPlugin
|
|
{
|
|
private:
|
|
// We need to be able to delete currentTest at the end of the
|
|
// test, to prevent CppUTest's leak-checking from triggering,
|
|
// due to an undeleted std::string - so we use std::unique_ptr.
|
|
std::unique_ptr<ApprovalTests::TestName> currentTest;
|
|
|
|
public:
|
|
ApprovalTestsCppUTestPlugin() : TestPlugin("ApprovalTestsCppUTestPlugin")
|
|
{
|
|
// Turn off CppUTest's leak checks.
|
|
// On some platforms, CppUTest's leak-checking reports leaks
|
|
// in this code, because the way the platform's std::string manages life-times
|
|
// of string storage is not compatible with the requirements of the
|
|
// CppUTest leak-checks.
|
|
MemoryLeakWarningPlugin::turnOffNewDeleteOverloads();
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD static std::string
|
|
cppUTestToString(const SimpleString& string)
|
|
{
|
|
return std::string{string.asCharString()};
|
|
}
|
|
|
|
void preTestAction(UtestShell& shell, TestResult& result) override
|
|
{
|
|
currentTest.reset(new ApprovalTests::TestName);
|
|
currentTest->setFileName(cppUTestToString(shell.getFile()));
|
|
|
|
currentTest->sections.emplace_back(cppUTestToString(shell.getGroup()));
|
|
currentTest->sections.emplace_back(cppUTestToString(shell.getName()));
|
|
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(currentTest.get());
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { CHECK_TRUE(true); });
|
|
TestPlugin::preTestAction(shell, result);
|
|
}
|
|
|
|
void postTestAction(UtestShell& shell, TestResult& result) override
|
|
{
|
|
currentTest = nullptr;
|
|
TestPlugin::postTestAction(shell, result);
|
|
}
|
|
};
|
|
|
|
inline void initializeApprovalTestsForCppUTest()
|
|
{
|
|
static ApprovalTests::ApprovalTestsCppUTestPlugin logPlugin;
|
|
TestRegistry::getCurrentRegistry()->installPlugin(&logPlugin);
|
|
}
|
|
}
|
|
|
|
#ifndef APPROVALS_CPPUTEST_EXISTING_MAIN
|
|
int main(int argc, char** argv)
|
|
{
|
|
ApprovalTests::initializeApprovalTestsForCppUTest();
|
|
|
|
int result = CommandLineTestRunner::RunAllTests(argc, argv);
|
|
TestRegistry::getCurrentRegistry()->resetPlugins();
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
#endif // APPROVALS_CPPUTEST
|
|
|
|
// ******************** From: DocTestApprovals.h
|
|
|
|
|
|
#if defined(APPROVALS_DOCTEST_EXISTING_MAIN)
|
|
#define APPROVALS_DOCTEST
|
|
#define DOCTEST_CONFIG_IMPLEMENT
|
|
#elif defined(APPROVALS_DOCTEST)
|
|
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
|
|
#endif
|
|
|
|
#ifdef APPROVALS_DOCTEST
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#include <doctest/doctest.h>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// anonymous namespace to prevent compiler -Wsubobject-linkage warnings
|
|
// This is OK as this code is only compiled on main()
|
|
namespace
|
|
{
|
|
struct AbstractReporter : doctest::IReporter
|
|
{
|
|
virtual void report_query(const doctest::QueryData&) override
|
|
{
|
|
}
|
|
// called when the whole test run starts
|
|
virtual void test_run_start() override
|
|
{
|
|
}
|
|
|
|
// called when the whole test run ends (caching a pointer to the input doesn't make sense here)
|
|
virtual void test_run_end(const doctest::TestRunStats&) override
|
|
{
|
|
}
|
|
|
|
// called when a test case is started (safe to cache a pointer to the input)
|
|
virtual void test_case_start(const doctest::TestCaseData&) override
|
|
{
|
|
}
|
|
|
|
#if 20305 <= DOCTEST_VERSION
|
|
// called when a test case is reentered because of unfinished subcases (safe to cache a pointer to the input)
|
|
virtual void test_case_reenter(const doctest::TestCaseData&) override
|
|
{
|
|
}
|
|
#endif
|
|
|
|
// called when a test case has ended
|
|
virtual void test_case_end(const doctest::CurrentTestCaseStats&) override
|
|
{
|
|
}
|
|
|
|
// called when an exception is thrown from the test case (or it crashes)
|
|
virtual void test_case_exception(const doctest::TestCaseException&) override
|
|
{
|
|
}
|
|
|
|
// called whenever a subcase is entered (don't cache pointers to the input)
|
|
virtual void subcase_start(const doctest::SubcaseSignature&) override
|
|
{
|
|
}
|
|
|
|
// called whenever a subcase is exited (don't cache pointers to the input)
|
|
virtual void subcase_end() override
|
|
{
|
|
}
|
|
|
|
// called for each assert (don't cache pointers to the input)
|
|
virtual void log_assert(const doctest::AssertData&) override
|
|
{
|
|
}
|
|
|
|
// called for each message (don't cache pointers to the input)
|
|
virtual void log_message(const doctest::MessageData&) override
|
|
{
|
|
}
|
|
|
|
// called when a test case is skipped either because it doesn't pass the filters, has a skip decorator
|
|
// or isn't in the execution range (between first and last) (safe to cache a pointer to the input)
|
|
virtual void test_case_skipped(const doctest::TestCaseData&) override
|
|
{
|
|
}
|
|
};
|
|
|
|
struct DocTestApprovalListener : AbstractReporter
|
|
{
|
|
TestName currentTest;
|
|
|
|
// constructor has to accept the ContextOptions by ref as a single argument
|
|
explicit DocTestApprovalListener(const doctest::ContextOptions& /*in*/)
|
|
{
|
|
}
|
|
|
|
std::string doctestToString(const doctest::String& string) const
|
|
{
|
|
return string.c_str();
|
|
}
|
|
|
|
std::string doctestToString(const char* string) const
|
|
{
|
|
return string;
|
|
}
|
|
|
|
void test_case_start(const doctest::TestCaseData& testInfo) override
|
|
{
|
|
currentTest.sections.emplace_back(testInfo.m_name);
|
|
currentTest.setFileName(doctestToString(testInfo.m_file));
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { REQUIRE(true); });
|
|
}
|
|
|
|
void test_case_end(const doctest::CurrentTestCaseStats& /*in*/) override
|
|
{
|
|
|
|
while (!currentTest.sections.empty())
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
}
|
|
|
|
void subcase_start(const doctest::SubcaseSignature& signature) override
|
|
{
|
|
currentTest.sections.emplace_back(doctestToString(signature.m_name));
|
|
}
|
|
|
|
void subcase_end() override
|
|
{
|
|
|
|
currentTest.sections.pop_back();
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
REGISTER_LISTENER("approvals", 0, ApprovalTests::DocTestApprovalListener);
|
|
|
|
#endif // APPROVALS_DOCTEST
|
|
|
|
// ******************** From: FmtApprovals.h
|
|
#ifdef FMT_VERSION
|
|
namespace ApprovalTests
|
|
{
|
|
using FmtApprovals =
|
|
ApprovalTests::TApprovals<ApprovalTests::ToStringCompileTimeOptions<FmtToString>>;
|
|
}
|
|
#endif
|
|
|
|
// ******************** From: GoogleConfiguration.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class GoogleConfiguration
|
|
{
|
|
public:
|
|
// This result is not used, it is only there to allow the method to execute, when this is used outside a function.
|
|
APPROVAL_TESTS_NO_DISCARD static bool addTestCaseNameRedundancyCheck(
|
|
GoogleCustomizationsFactory::Comparator comparator);
|
|
|
|
// This result is not used, it is only there to allow the method to execute, when this is used outside a function.
|
|
APPROVAL_TESTS_NO_DISCARD static bool
|
|
addIgnorableTestCaseNameSuffix(std::string suffix);
|
|
|
|
static GoogleCustomizationsFactory::Comparator
|
|
createIgnorableTestCaseNameSuffixCheck(const std::string& suffix);
|
|
};
|
|
}
|
|
|
|
// ******************** From: GoogleTestApprovals.h
|
|
|
|
|
|
#ifdef APPROVALS_GOOGLETEST_EXISTING_MAIN
|
|
#define APPROVALS_GOOGLETEST
|
|
#endif
|
|
|
|
#ifdef APPROVALS_GOOGLETEST
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#include <gtest/gtest.h>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class GoogleTestListener : public testing::EmptyTestEventListener
|
|
{
|
|
TestName currentTest;
|
|
|
|
public:
|
|
bool isDuplicate(std::string testFileNameWithExtension, std::string testCaseName)
|
|
{
|
|
for (auto check : GoogleCustomizationsFactory::getEquivalencyChecks())
|
|
{
|
|
if (check(testFileNameWithExtension, testCaseName))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
virtual void OnTestStart(const testing::TestInfo& testInfo) override
|
|
{
|
|
currentTest.setFileName(testInfo.file());
|
|
currentTest.sections = {};
|
|
if (!isDuplicate(currentTest.getFileName(), testInfo.test_case_name()))
|
|
{
|
|
currentTest.sections.emplace_back(testInfo.test_case_name());
|
|
}
|
|
if (!std::string(testInfo.name()).empty())
|
|
{
|
|
currentTest.sections.emplace_back(testInfo.name());
|
|
}
|
|
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { EXPECT_TRUE(true); });
|
|
}
|
|
};
|
|
|
|
inline void initializeApprovalTestsForGoogleTests()
|
|
{
|
|
auto& listeners = testing::UnitTest::GetInstance()->listeners();
|
|
listeners.Append(new GoogleTestListener);
|
|
}
|
|
}
|
|
|
|
#ifndef APPROVALS_GOOGLETEST_EXISTING_MAIN
|
|
int main(int argc, char** argv)
|
|
{
|
|
::testing::InitGoogleTest(&argc, argv);
|
|
ApprovalTests::initializeApprovalTestsForGoogleTests();
|
|
return RUN_ALL_TESTS();
|
|
}
|
|
#endif //APPROVALS_GOOGLETEST_EXISTING_MAIN
|
|
|
|
#endif
|
|
|
|
// ******************** From: UTApprovals.h
|
|
|
|
|
|
#ifdef APPROVALS_UT
|
|
#define APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#if !(__GNUC__ >= 9 or __clang_major__ >= 9)
|
|
#error \
|
|
"The [Boost].UT integration with Approval Tests requires source_location support by the compiler"
|
|
#endif
|
|
|
|
#include <boost/ut.hpp>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace cfg
|
|
{
|
|
void notify_success();
|
|
|
|
class reporter : public boost::ut::reporter<boost::ut::printer>
|
|
{
|
|
private:
|
|
TestName currentTest;
|
|
|
|
public:
|
|
auto on(boost::ut::events::test_begin test_begin) -> void
|
|
{
|
|
std::string name = std::string(test_begin.name);
|
|
currentTest.sections.emplace_back(name);
|
|
currentTest.setFileName(test_begin.location.file_name());
|
|
|
|
ApprovalTests::FrameworkIntegrations::setCurrentTest(¤tTest);
|
|
ApprovalTests::FrameworkIntegrations::setTestPassedNotification(
|
|
[]() { notify_success(); });
|
|
boost::ut::reporter<boost::ut::printer>::on(test_begin);
|
|
}
|
|
|
|
auto on(boost::ut::events::test_run test_run) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(test_run);
|
|
}
|
|
|
|
auto on(boost::ut::events::test_skip test_skip) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(test_skip);
|
|
}
|
|
|
|
auto on(boost::ut::events::test_end test_end) -> void
|
|
{
|
|
while (!currentTest.sections.empty())
|
|
{
|
|
currentTest.sections.pop_back();
|
|
}
|
|
boost::ut::reporter<boost::ut::printer>::on(test_end);
|
|
}
|
|
|
|
template <class TMsg> auto on(boost::ut::events::log<TMsg> log) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(log);
|
|
}
|
|
|
|
template <class TExpr>
|
|
auto on(boost::ut::events::assertion_pass<TExpr> location) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(location);
|
|
}
|
|
|
|
template <class TExpr>
|
|
auto on(boost::ut::events::assertion_fail<TExpr> fail) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(fail);
|
|
}
|
|
|
|
auto on(boost::ut::events::fatal_assertion fatal) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(fatal);
|
|
}
|
|
|
|
auto on(boost::ut::events::exception exception) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(exception);
|
|
}
|
|
|
|
auto on(boost::ut::events::summary summary) -> void
|
|
{
|
|
boost::ut::reporter<boost::ut::printer>::on(summary);
|
|
}
|
|
};
|
|
} // namespace cfg
|
|
}
|
|
|
|
template <>
|
|
auto boost::ut::cfg<boost::ut::override> =
|
|
boost::ut::runner<ApprovalTests::cfg::reporter>{};
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace cfg
|
|
{
|
|
void notify_success()
|
|
{
|
|
// This needs to be after the registering of our custom listener,
|
|
// for compilation to succeed.
|
|
boost::ut::expect(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif // APPROVALS_UT
|
|
|
|
// ******************** From: HelpMessages.h
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class HelpMessages
|
|
{
|
|
public:
|
|
static std::string getMisconfiguredBuildHelp(const std::string& fileName);
|
|
|
|
static std::string getMisconfiguredMainHelp();
|
|
|
|
static std::string getUnconfiguredRootDirectory();
|
|
|
|
static std::string
|
|
getUnknownEnvVarReporterHelp(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns);
|
|
static std::string
|
|
getInvalidEnvVarReporterHelp(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns);
|
|
|
|
static std::string envVarErrorMessage(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns,
|
|
std::string& helpMessage);
|
|
|
|
static std::string topAndTailHelpMessage(const std::string& message);
|
|
};
|
|
}
|
|
|
|
// ******************** From: SeparateApprovedAndReceivedDirectoriesNamer.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class SeparateApprovedAndReceivedDirectoriesNamer : public TemplatedCustomNamer
|
|
{
|
|
public:
|
|
SeparateApprovedAndReceivedDirectoriesNamer();
|
|
|
|
virtual ~SeparateApprovedAndReceivedDirectoriesNamer() override = default;
|
|
|
|
static DefaultNamerDisposer useAsDefaultNamer();
|
|
};
|
|
}
|
|
|
|
// ******************** From: AutoApproveIfMissingReporter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class AutoApproveIfMissingReporter : public Reporter
|
|
{
|
|
public:
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: BlockingReporter.h
|
|
|
|
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class BlockingReporter : public Reporter
|
|
{
|
|
private:
|
|
std::shared_ptr<Blocker> blocker;
|
|
|
|
BlockingReporter() = delete;
|
|
|
|
public:
|
|
explicit BlockingReporter(std::shared_ptr<Blocker> blocker_);
|
|
|
|
static std::shared_ptr<BlockingReporter>
|
|
onMachineNamed(const std::string& machineName);
|
|
|
|
static std::shared_ptr<BlockingReporter>
|
|
onMachinesNotNamed(const std::string& machineName);
|
|
|
|
virtual bool report(std::string /*received*/,
|
|
std::string /*approved*/) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: CIBuildOnlyReporter.h
|
|
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// A reporter which uses the supplied reporter, if called on a supported Continuous Integration system
|
|
class CIBuildOnlyReporter : public Reporter
|
|
{
|
|
private:
|
|
std::shared_ptr<Reporter> m_reporter;
|
|
|
|
public:
|
|
explicit CIBuildOnlyReporter(
|
|
std::shared_ptr<Reporter> reporter = std::make_shared<TextDiffReporter>());
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
|
|
static bool isRunningUnderCI();
|
|
};
|
|
} // namespace ApprovalTests
|
|
|
|
// ******************** From: CIBuildOnlyReporterUtils.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace CIBuildOnlyReporterUtils
|
|
{
|
|
FrontLoadedReporterDisposer
|
|
useAsFrontLoadedReporter(const std::shared_ptr<Reporter>& reporter);
|
|
}
|
|
} // namespace ApprovalTests
|
|
|
|
// ******************** From: ClipboardReporter.h
|
|
|
|
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ClipboardReporter : public Reporter
|
|
{
|
|
public:
|
|
static std::string getCommandLineFor(const std::string& received,
|
|
const std::string& approved,
|
|
bool isWindows);
|
|
|
|
virtual bool report(std::string received, std::string approved) const override;
|
|
|
|
static void copyToClipboard(const std::string& newClipboard);
|
|
};
|
|
}
|
|
|
|
// ******************** From: CombinationReporter.h
|
|
|
|
#include <memory>
|
|
#include <vector>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class CombinationReporter : public Reporter
|
|
{
|
|
private:
|
|
std::vector<std::unique_ptr<Reporter>> reporters;
|
|
|
|
public:
|
|
// Note that CombinationReporter takes ownership of the given Reporter objects
|
|
explicit CombinationReporter(const std::vector<Reporter*>& theReporters);
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
|
|
// ******************** From: CrossPlatformReporters.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace CrossPlatform
|
|
{
|
|
class VisualStudioCodeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
VisualStudioCodeReporter();
|
|
};
|
|
|
|
class CrossPlatformDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
CrossPlatformDiffReporter();
|
|
};
|
|
}
|
|
}
|
|
|
|
// ******************** From: CustomReporter.h
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class CustomReporter
|
|
{
|
|
public:
|
|
static std::shared_ptr<GenericDiffReporter> create(std::string path,
|
|
Type type = Type::TEXT);
|
|
|
|
static std::shared_ptr<GenericDiffReporter>
|
|
create(std::string path, std::string arguments, Type type = Type::TEXT);
|
|
|
|
static std::shared_ptr<GenericDiffReporter> createForegroundReporter(
|
|
std::string path, Type type = Type::TEXT, bool allowNonZeroExitCodes = false);
|
|
|
|
static std::shared_ptr<GenericDiffReporter>
|
|
createForegroundReporter(std::string path,
|
|
std::string arguments,
|
|
Type type = Type::TEXT,
|
|
bool allowNonZeroExitCodes = false);
|
|
};
|
|
}
|
|
|
|
// ******************** From: DiffReporter.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class DiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
DiffReporter();
|
|
};
|
|
}
|
|
|
|
// ******************** From: EnvironmentVariableReporter.h
|
|
|
|
#include <memory>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class EnvironmentVariableReporter : public Reporter
|
|
{
|
|
public:
|
|
bool report(std::string received, std::string approved) const override;
|
|
|
|
bool report(const std::string& envVar,
|
|
const std::string& received,
|
|
const std::string& approved) const;
|
|
|
|
static std::string environmentVariableName();
|
|
|
|
private:
|
|
ReporterFactory factory;
|
|
};
|
|
}
|
|
|
|
// ******************** From: LinuxReporters.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Linux
|
|
{
|
|
class SublimeMergeSnapReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeSnapReporter();
|
|
};
|
|
|
|
class SublimeMergeFlatpakReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeFlatpakReporter();
|
|
};
|
|
|
|
class SublimeMergeRepositoryPackageReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeRepositoryPackageReporter();
|
|
};
|
|
|
|
class SublimeMergeDirectDownloadReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeDirectDownloadReporter();
|
|
};
|
|
|
|
class SublimeMergeReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
SublimeMergeReporter();
|
|
};
|
|
|
|
class KDiff3Reporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
KDiff3Reporter();
|
|
};
|
|
|
|
class MeldReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
MeldReporter();
|
|
};
|
|
|
|
class BeyondCompareReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
BeyondCompareReporter();
|
|
};
|
|
|
|
class LinuxDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
LinuxDiffReporter();
|
|
};
|
|
}
|
|
}
|
|
|
|
// ******************** From: MacReporters.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Mac
|
|
{
|
|
class DiffMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
DiffMergeReporter();
|
|
};
|
|
|
|
class AraxisMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
AraxisMergeReporter();
|
|
};
|
|
|
|
class VisualStudioCodeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
VisualStudioCodeReporter();
|
|
};
|
|
|
|
class BeyondCompareReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
BeyondCompareReporter();
|
|
};
|
|
|
|
class KaleidoscopeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
KaleidoscopeReporter();
|
|
};
|
|
|
|
class SublimeMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeReporter();
|
|
};
|
|
|
|
class KDiff3Reporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
KDiff3Reporter();
|
|
};
|
|
|
|
class P4MergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
P4MergeReporter();
|
|
};
|
|
|
|
class TkDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
TkDiffReporter();
|
|
};
|
|
|
|
// Note that this will be found on Linux too.
|
|
// See https://github.com/approvals/ApprovalTests.cpp/issues/138 for limitations
|
|
class CLionDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
CLionDiffReporter();
|
|
};
|
|
|
|
class MacDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
MacDiffReporter();
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
}
|
|
|
|
// ******************** From: WindowsReporters.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Windows
|
|
{
|
|
|
|
class VisualStudioCodeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
VisualStudioCodeReporter();
|
|
};
|
|
|
|
// ----------------------- Beyond Compare ----------------------------------
|
|
class BeyondCompare3Reporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
BeyondCompare3Reporter();
|
|
};
|
|
|
|
class BeyondCompare4Reporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
BeyondCompare4Reporter();
|
|
};
|
|
|
|
class BeyondCompareReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
BeyondCompareReporter();
|
|
};
|
|
|
|
// ----------------------- Tortoise SVN ------------------------------------
|
|
class TortoiseImageDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
TortoiseImageDiffReporter();
|
|
};
|
|
|
|
class TortoiseTextDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
TortoiseTextDiffReporter();
|
|
};
|
|
|
|
class TortoiseDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
TortoiseDiffReporter();
|
|
};
|
|
|
|
// ----------------------- Tortoise Git ------------------------------------
|
|
class TortoiseGitTextDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
TortoiseGitTextDiffReporter();
|
|
};
|
|
|
|
class TortoiseGitImageDiffReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
TortoiseGitImageDiffReporter();
|
|
};
|
|
|
|
class TortoiseGitDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
TortoiseGitDiffReporter();
|
|
};
|
|
|
|
// -------------------------------------------------------------------------
|
|
class WinMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
WinMergeReporter();
|
|
};
|
|
|
|
class AraxisMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
AraxisMergeReporter();
|
|
};
|
|
|
|
class CodeCompareReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
CodeCompareReporter();
|
|
};
|
|
|
|
class SublimeMergeReporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
SublimeMergeReporter();
|
|
};
|
|
|
|
class KDiff3Reporter : public GenericDiffReporter
|
|
{
|
|
public:
|
|
KDiff3Reporter();
|
|
};
|
|
|
|
class WindowsDiffReporter : public FirstWorkingReporter
|
|
{
|
|
public:
|
|
WindowsDiffReporter();
|
|
|
|
bool report(std::string received, std::string approved) const override;
|
|
};
|
|
}
|
|
}
|
|
|
|
// ******************** From: DateUtils.h
|
|
|
|
#include <chrono>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// All values are in UTC
|
|
class DateUtils
|
|
{
|
|
public:
|
|
static std::tm
|
|
createTm(int year, int month, int day, int hour, int minute, int second);
|
|
|
|
static std::chrono::system_clock::time_point
|
|
createUtcDateTime(int year, int month, int day, int hour, int minute, int second);
|
|
|
|
static std::string toString(const std::chrono::system_clock::time_point& dateTime,
|
|
const std::string& format);
|
|
|
|
static std::string
|
|
toString(const std::chrono::system_clock::time_point& dateTime);
|
|
|
|
static time_t toUtc(std::tm& timeinfo);
|
|
|
|
static tm toUtc(time_t& tt);
|
|
};
|
|
}
|
|
|
|
// ******************** From: EmptyFileCreatorByType.h
|
|
|
|
|
|
#include <map>
|
|
#include <string>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class EmptyFileCreatorByType
|
|
{
|
|
private:
|
|
static std::map<std::string, EmptyFileCreator> creators_;
|
|
|
|
public:
|
|
static void registerCreator(const std::string& extensionWithDot,
|
|
EmptyFileCreator creator);
|
|
|
|
static void createFile(const std::string& fileName);
|
|
};
|
|
}
|
|
|
|
// ******************** From: ExceptionCollector.h
|
|
|
|
#include <sstream>
|
|
#include <exception>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class ExceptionCollector
|
|
{
|
|
std::vector<std::string> exceptionMessages;
|
|
|
|
public:
|
|
void gather(std::function<void(void)> functionThatThrows);
|
|
|
|
~ExceptionCollector();
|
|
|
|
void release();
|
|
};
|
|
}
|
|
|
|
// ******************** From: FileUtilsSystemSpecific.h
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class FileUtilsSystemSpecific
|
|
{
|
|
public:
|
|
static std::string getCommandLineForCopy(const std::string& source,
|
|
const std::string& destination,
|
|
bool isWindows);
|
|
|
|
static void copyFile(const std::string& source, const std::string& destination);
|
|
};
|
|
}
|
|
|
|
// ******************** From: Grid.h
|
|
#include <sstream>
|
|
#include <functional>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
class Grid
|
|
{
|
|
public:
|
|
static std::string print(int width,
|
|
int height,
|
|
std::function<void(int, int, std::ostream&)> printCell);
|
|
static std::string print(int width, int height, std::string text);
|
|
};
|
|
}
|
|
|
|
#ifdef APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
// ******************** From: ApprovalUtils.cpp
|
|
|
|
#include <ostream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
void ApprovalUtils::writeHeader(std::ostream& stream, const std::string& header)
|
|
{
|
|
if (!header.empty())
|
|
{
|
|
stream << header << "\n\n\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: Storyboard.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
Storyboard& Storyboard::addDescription(const std::string& description)
|
|
{
|
|
output_ << description << "\n";
|
|
addNewLineBeforeNextFrame_ = true;
|
|
return *this;
|
|
}
|
|
|
|
Storyboard& Storyboard::addDescriptionWithData(const std::string& description,
|
|
const std::string& data)
|
|
{
|
|
output_ << description << ": " << data << "\n";
|
|
addNewLineBeforeNextFrame_ = true;
|
|
return *this;
|
|
}
|
|
|
|
Storyboard& Storyboard::addFrame(const std::string& frame)
|
|
{
|
|
if (frameCount_ == 0)
|
|
{
|
|
return addFrame("Initial Frame", frame);
|
|
}
|
|
else
|
|
{
|
|
return addFrame("Frame #" + std::to_string(frameCount_), frame);
|
|
}
|
|
}
|
|
|
|
Storyboard& Storyboard::addFrame(const std::string& title, const std::string& frame)
|
|
{
|
|
if (addNewLineBeforeNextFrame_)
|
|
{
|
|
output_ << '\n';
|
|
addNewLineBeforeNextFrame_ = false;
|
|
}
|
|
output_ << title << ":\n";
|
|
output_ << frame << "\n\n";
|
|
frameCount_ += 1;
|
|
return *this;
|
|
}
|
|
|
|
Storyboard& Storyboard::addFrames(int numberOfFrames,
|
|
const std::function<std::string(int)>& function)
|
|
{
|
|
for (int frame = 1; frame <= numberOfFrames; ++frame)
|
|
{
|
|
addFrame(function(frame));
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
std::ostream& operator<<(std::ostream& os, const Storyboard& board)
|
|
{
|
|
os << board.output_.str();
|
|
return os;
|
|
}
|
|
}
|
|
|
|
// ******************** From: ComparatorDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
ComparatorDisposer::ComparatorDisposer(
|
|
ComparatorContainer& comparators_,
|
|
const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalTests::ApprovalComparator> previousComparator_,
|
|
std::shared_ptr<ApprovalTests::ApprovalComparator> newComparator)
|
|
: comparators(comparators_)
|
|
, ext_(extensionWithDot)
|
|
, previousComparator(std::move(previousComparator_))
|
|
{
|
|
comparators_[extensionWithDot] = std::move(newComparator);
|
|
}
|
|
|
|
ComparatorDisposer::ComparatorDisposer(ComparatorDisposer&& other) noexcept
|
|
: comparators(other.comparators)
|
|
, ext_(std::move(other.ext_))
|
|
, previousComparator(std::move(other.previousComparator))
|
|
{
|
|
other.isActive = false;
|
|
}
|
|
|
|
ComparatorDisposer::~ComparatorDisposer()
|
|
{
|
|
if (isActive)
|
|
{
|
|
comparators[ext_] = previousComparator;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: ComparatorFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
ComparatorContainer& ComparatorFactory::comparators()
|
|
{
|
|
static ComparatorContainer allComparators;
|
|
return allComparators;
|
|
}
|
|
|
|
ComparatorDisposer
|
|
ComparatorFactory::registerComparator(const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalComparator> comparator)
|
|
{
|
|
return ComparatorDisposer(comparators(),
|
|
extensionWithDot,
|
|
getComparatorForFileExtensionWithDot(extensionWithDot),
|
|
comparator);
|
|
}
|
|
|
|
std::shared_ptr<ApprovalComparator>
|
|
ComparatorFactory::getComparatorForFile(const std::string& receivedPath)
|
|
{
|
|
const std::string fileExtension = FileUtils::getExtensionWithDot(receivedPath);
|
|
return getComparatorForFileExtensionWithDot(fileExtension);
|
|
}
|
|
|
|
std::shared_ptr<ApprovalComparator>
|
|
ComparatorFactory::getComparatorForFileExtensionWithDot(
|
|
const std::string& fileExtensionWithDot)
|
|
{
|
|
auto iterator = comparators().find(fileExtensionWithDot);
|
|
if (iterator != comparators().end())
|
|
{
|
|
return iterator->second;
|
|
}
|
|
return std::make_shared<TextFileComparator>();
|
|
}
|
|
}
|
|
|
|
// ******************** From: TextFileComparator.cpp
|
|
|
|
#include <fstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::ifstream::int_type
|
|
TextFileComparator::getNextRelevantCharacter(std::ifstream& astream)
|
|
{
|
|
auto ch = astream.get();
|
|
if (ch == '\r')
|
|
{
|
|
return astream.get();
|
|
}
|
|
else
|
|
{
|
|
return ch;
|
|
}
|
|
}
|
|
|
|
bool TextFileComparator::contentsAreEquivalent(std::string receivedPath,
|
|
std::string approvedPath) const
|
|
{
|
|
std::ifstream astream(approvedPath.c_str(), std::ios::binary | std::ifstream::in);
|
|
std::ifstream rstream(receivedPath.c_str(), std::ios::binary | std::ifstream::in);
|
|
|
|
while (astream.good() && rstream.good())
|
|
{
|
|
int a = getNextRelevantCharacter(astream);
|
|
int r = getNextRelevantCharacter(rstream);
|
|
|
|
if (a != r)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ******************** From: ApprovalException.cpp
|
|
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
ApprovalException::ApprovalException(const std::string& msg) : message(msg)
|
|
{
|
|
}
|
|
|
|
const char* ApprovalException::what() const noexcept
|
|
{
|
|
return message.c_str();
|
|
}
|
|
|
|
std::string ApprovalMismatchException::format(const std::string& received,
|
|
const std::string& approved)
|
|
{
|
|
std::stringstream s;
|
|
s << "Failed Approval: \n"
|
|
<< "Received does not match approved \n"
|
|
<< "Received : \"" << received << "\" \n"
|
|
<< "Approved : \"" << approved << "\"";
|
|
return s.str();
|
|
}
|
|
|
|
ApprovalMismatchException::ApprovalMismatchException(const std::string& received,
|
|
const std::string& approved)
|
|
: ApprovalException(format(received, approved))
|
|
{
|
|
}
|
|
|
|
std::string ApprovalMissingException::format(const std::string& file)
|
|
{
|
|
std::stringstream s;
|
|
s << "Failed Approval: \n"
|
|
<< "Approval File Not Found \n"
|
|
<< "File: \"" << file << '"';
|
|
return s.str();
|
|
}
|
|
|
|
ApprovalMissingException::ApprovalMissingException(const std::string&,
|
|
const std::string& approved)
|
|
: ApprovalException(format(approved))
|
|
{
|
|
}
|
|
}
|
|
|
|
// ******************** From: FileApprover.cpp
|
|
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
ComparatorDisposer FileApprover::registerComparatorForExtension(
|
|
const std::string& extensionWithDot,
|
|
std::shared_ptr<ApprovalComparator> comparator)
|
|
{
|
|
return ComparatorFactory::registerComparator(extensionWithDot, comparator);
|
|
}
|
|
|
|
FileApprover::TestPassedNotification FileApprover::testPassedNotification_ = []() {};
|
|
|
|
void FileApprover::verify(const std::string& receivedPath,
|
|
const std::string& approvedPath,
|
|
const ApprovalComparator& comparator)
|
|
{
|
|
if (receivedPath == approvedPath)
|
|
{
|
|
std::stringstream s;
|
|
s << "Identical filenames for received and approved.\n"
|
|
<< "Tests would spuriously pass. \n"
|
|
<< "Please check your custom namer. \n"
|
|
<< "Received : \"" << receivedPath << "\" \n"
|
|
<< "Approved : \"" << approvedPath << "\"";
|
|
// Do not throw ApprovalException, as we don't want reporters to trigger.
|
|
// They would show two seemingly identical files, due to matching file name.
|
|
throw std::runtime_error(s.str());
|
|
}
|
|
|
|
if (!FileUtils::fileExists(approvedPath))
|
|
{
|
|
throw ApprovalMissingException(receivedPath, approvedPath);
|
|
}
|
|
|
|
if (!FileUtils::fileExists(receivedPath))
|
|
{
|
|
throw ApprovalMissingException(approvedPath, receivedPath);
|
|
}
|
|
|
|
if (!comparator.contentsAreEquivalent(receivedPath, approvedPath))
|
|
{
|
|
throw ApprovalMismatchException(receivedPath, approvedPath);
|
|
}
|
|
}
|
|
|
|
void FileApprover::verify(const std::string& receivedPath,
|
|
const std::string& approvedPath)
|
|
{
|
|
verify(receivedPath,
|
|
approvedPath,
|
|
*ComparatorFactory::getComparatorForFile(receivedPath));
|
|
}
|
|
|
|
void FileApprover::verify(const ApprovalNamer& n,
|
|
const ApprovalWriter& s,
|
|
const Reporter& r)
|
|
{
|
|
std::string approvedPath = n.getApprovedFile(s.getFileExtensionWithDot());
|
|
std::string receivedPath = n.getReceivedFile(s.getFileExtensionWithDot());
|
|
SystemUtils::ensureParentDirectoryExists(approvedPath);
|
|
SystemUtils::ensureParentDirectoryExists(receivedPath);
|
|
s.write(receivedPath);
|
|
try
|
|
{
|
|
verify(receivedPath, approvedPath);
|
|
s.cleanUpReceived(receivedPath);
|
|
notifyTestPassed();
|
|
}
|
|
catch (const ApprovalException&)
|
|
{
|
|
reportAfterTryingFrontLoadedReporter(receivedPath, approvedPath, r);
|
|
throw;
|
|
}
|
|
}
|
|
|
|
void
|
|
FileApprover::reportAfterTryingFrontLoadedReporter(const std::string& receivedPath,
|
|
const std::string& approvedPath,
|
|
const Reporter& r)
|
|
{
|
|
auto tryFirst = FrontLoadedReporterFactory::getFrontLoadedReporter();
|
|
if (!tryFirst->report(receivedPath, approvedPath))
|
|
{
|
|
r.report(receivedPath, approvedPath);
|
|
}
|
|
}
|
|
|
|
void FileApprover::setTestPassedNotification(
|
|
FileApprover::TestPassedNotification notification)
|
|
{
|
|
testPassedNotification_ = notification;
|
|
}
|
|
|
|
void FileApprover::notifyTestPassed()
|
|
{
|
|
testPassedNotification_();
|
|
}
|
|
}
|
|
|
|
// ******************** From: Options.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
// FileOptions -----------------------------------------------------------------------
|
|
Options::FileOptions::FileOptions(std::string fileExtensionWithDot)
|
|
: fileExtensionWithDot_(std::move(fileExtensionWithDot))
|
|
{
|
|
}
|
|
|
|
Options::FileOptions Options::FileOptions::clone() const
|
|
{
|
|
// the returned options_ must be null
|
|
return FileOptions(fileExtensionWithDot_);
|
|
}
|
|
|
|
const std::string& Options::FileOptions::getFileExtension() const
|
|
{
|
|
return fileExtensionWithDot_;
|
|
}
|
|
|
|
Options
|
|
Options::FileOptions::withFileExtension(const std::string& fileExtensionWithDot) const
|
|
{
|
|
FileOptions newSelf(fileExtensionWithDot);
|
|
return options_->clone(newSelf);
|
|
}
|
|
|
|
// Options ---------------------------------------------------------------------------
|
|
Options::Options(Options::FileOptions fileOptions,
|
|
Scrubber scrubber,
|
|
const Reporter& reporter,
|
|
bool usingDefaultScrubber,
|
|
std::shared_ptr<ApprovalNamer> namer)
|
|
: fileOptions_(std::move(fileOptions))
|
|
, scrubber_(std::move(scrubber))
|
|
, reporter_(reporter)
|
|
, namer_(namer)
|
|
, usingDefaultScrubber_(usingDefaultScrubber)
|
|
{
|
|
}
|
|
|
|
Options Options::clone(const Options::FileOptions& fileOptions) const
|
|
{
|
|
// TODO error this can retain a previous Options* ???
|
|
return Options(fileOptions, scrubber_, reporter_, usingDefaultScrubber_, namer_);
|
|
}
|
|
|
|
const Reporter& Options::defaultReporter()
|
|
{
|
|
static DefaultReporter defaultReporter;
|
|
return defaultReporter;
|
|
}
|
|
|
|
Options::Options(Scrubber scrubber) : scrubber_(std::move(scrubber))
|
|
{
|
|
usingDefaultScrubber_ = false;
|
|
}
|
|
|
|
Options::Options(const Reporter& reporter) : reporter_(reporter)
|
|
{
|
|
}
|
|
|
|
Options::FileOptions Options::fileOptions() const
|
|
{
|
|
if (fileOptions_.options_ != nullptr)
|
|
{
|
|
throw std::logic_error(
|
|
"Incorrect assumption: A FileOptions has been re-used");
|
|
}
|
|
FileOptions copy = fileOptions_.clone();
|
|
copy.options_ = this;
|
|
return copy;
|
|
}
|
|
|
|
Scrubber Options::getScrubber() const
|
|
{
|
|
return scrubber_;
|
|
}
|
|
|
|
bool Options::isUsingDefaultScrubber() const
|
|
{
|
|
return usingDefaultScrubber_;
|
|
}
|
|
|
|
std::string Options::scrub(const std::string& input) const
|
|
{
|
|
return scrubber_(input);
|
|
}
|
|
const Reporter& Options::getReporter() const
|
|
{
|
|
return reporter_;
|
|
}
|
|
|
|
Options Options::withReporter(const Reporter& reporter) const
|
|
{
|
|
return Options(fileOptions_, scrubber_, reporter, usingDefaultScrubber_, namer_);
|
|
}
|
|
|
|
Options Options::withScrubber(Scrubber scrubber) const
|
|
{
|
|
return Options(fileOptions_, std::move(scrubber), reporter_, false, namer_);
|
|
}
|
|
|
|
std::shared_ptr<ApprovalNamer> Options::getNamer() const
|
|
{
|
|
return namer_;
|
|
}
|
|
|
|
Options Options::withNamer(std::shared_ptr<ApprovalNamer> namer)
|
|
{
|
|
return Options(
|
|
fileOptions_, scrubber_, reporter_, usingDefaultScrubber_, std::move(namer));
|
|
}
|
|
}
|
|
|
|
// ******************** From: FrameworkIntegrations.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
void FrameworkIntegrations::setTestPassedNotification(
|
|
FileApprover::TestPassedNotification notification)
|
|
{
|
|
FileApprover::setTestPassedNotification(notification);
|
|
}
|
|
|
|
void FrameworkIntegrations::setCurrentTest(ApprovalTests::TestName* currentTest)
|
|
{
|
|
ApprovalTestNamer::currentTest(currentTest);
|
|
}
|
|
}
|
|
|
|
// ******************** From: GoogleConfiguration.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool GoogleConfiguration::addTestCaseNameRedundancyCheck(
|
|
GoogleCustomizationsFactory::Comparator comparator)
|
|
{
|
|
return GoogleCustomizationsFactory::addTestCaseNameRedundancyCheck(comparator);
|
|
}
|
|
|
|
bool GoogleConfiguration::addIgnorableTestCaseNameSuffix(std::string suffix)
|
|
{
|
|
return addTestCaseNameRedundancyCheck(
|
|
createIgnorableTestCaseNameSuffixCheck(suffix));
|
|
}
|
|
|
|
GoogleCustomizationsFactory::Comparator
|
|
GoogleConfiguration::createIgnorableTestCaseNameSuffixCheck(const std::string& suffix)
|
|
{
|
|
return [suffix](std::string testFileNameWithExtension, std::string testCaseName) {
|
|
if (testCaseName.length() <= suffix.length() ||
|
|
!StringUtils::endsWith(testCaseName, suffix))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto withoutSuffix =
|
|
testCaseName.substr(0, testCaseName.length() - suffix.length());
|
|
auto withFileExtension = withoutSuffix + ".";
|
|
return StringUtils::contains(testFileNameWithExtension, withFileExtension);
|
|
};
|
|
}
|
|
}
|
|
|
|
// ******************** From: GoogleCustomizationsFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
GoogleCustomizationsFactory::ComparatorContainer&
|
|
GoogleCustomizationsFactory::comparatorContainer()
|
|
{
|
|
static ComparatorContainer container;
|
|
if (container.empty())
|
|
{
|
|
auto exactNameMatching = [](const std::string& testFileNameWithExtension,
|
|
const std::string& testCaseName) {
|
|
return StringUtils::contains(testFileNameWithExtension,
|
|
testCaseName + ".");
|
|
};
|
|
container.push_back(exactNameMatching);
|
|
}
|
|
return container;
|
|
}
|
|
|
|
GoogleCustomizationsFactory::ComparatorContainer
|
|
GoogleCustomizationsFactory::getEquivalencyChecks()
|
|
{
|
|
return comparatorContainer();
|
|
}
|
|
|
|
bool GoogleCustomizationsFactory::addTestCaseNameRedundancyCheck(
|
|
const GoogleCustomizationsFactory::Comparator& comparator)
|
|
{
|
|
comparatorContainer().push_back(comparator);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ******************** From: SystemLauncher.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
SystemLauncher::SystemLauncher(bool isForeground, bool allowNonZeroExitCodes)
|
|
: isForeground_(isForeground), allowNonZeroExitCodes_(allowNonZeroExitCodes)
|
|
{
|
|
}
|
|
|
|
bool SystemLauncher::launch(const std::string& commandLine)
|
|
{
|
|
std::string launch = getCommandLine(commandLine);
|
|
|
|
SystemUtils::runSystemCommandOrThrow(launch, allowNonZeroExitCodes_);
|
|
return true;
|
|
}
|
|
|
|
void SystemLauncher::invokeForWindows(bool useWindows)
|
|
{
|
|
useWindows_ = useWindows;
|
|
}
|
|
|
|
void SystemLauncher::setForeground(bool foreground)
|
|
{
|
|
isForeground_ = foreground;
|
|
}
|
|
|
|
void SystemLauncher::setAllowNonZeroExitCodes(bool allow)
|
|
{
|
|
allowNonZeroExitCodes_ = allow;
|
|
}
|
|
|
|
bool SystemLauncher::isForeground() const
|
|
{
|
|
return isForeground_;
|
|
}
|
|
|
|
std::string SystemLauncher::getCommandLine(const std::string& commandLine) const
|
|
{
|
|
std::string launch = useWindows_
|
|
? getWindowsCommandLine(commandLine, isForeground_)
|
|
: getUnixCommandLine(commandLine, isForeground_);
|
|
return launch;
|
|
}
|
|
|
|
std::string SystemLauncher::getWindowsCommandLine(const std::string& commandLine,
|
|
bool foreground) const
|
|
{
|
|
std::string launch = foreground
|
|
? (std::string("cmd /S /C ") + "\"" + commandLine + "\"")
|
|
: ("start \"\" " + commandLine);
|
|
|
|
return launch;
|
|
}
|
|
|
|
std::string SystemLauncher::getUnixCommandLine(const std::string& commandLine,
|
|
bool foreground) const
|
|
{
|
|
std::string launch = foreground ? commandLine : (commandLine + " &");
|
|
|
|
return launch;
|
|
}
|
|
}
|
|
|
|
// ******************** From: ApprovalTestNamer.cpp
|
|
|
|
#include <iostream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string TestName::directoryPrefix;
|
|
bool TestName::checkBuildConfig_ = true;
|
|
|
|
const std::string& TestName::getFileName() const
|
|
{
|
|
checkBuildConfiguration(fileName);
|
|
return fileName;
|
|
}
|
|
|
|
std::string TestName::getOriginalFileName()
|
|
{
|
|
return originalFileName;
|
|
}
|
|
|
|
void TestName::setFileName(const std::string& file)
|
|
{
|
|
originalFileName = file;
|
|
fileName = file.empty() ? handleBoostQuirks() : findFileName(file);
|
|
}
|
|
|
|
std::string TestName::findFileName(const std::string& file)
|
|
{
|
|
auto newFileName = checkParentDirectoriesForFile(file);
|
|
return SystemUtils::checkFilenameCase(newFileName);
|
|
}
|
|
|
|
std::string& TestName::rootDirectoryStorage()
|
|
{
|
|
// This method is needed to fix static initialisation order
|
|
static std::string rootDirectory;
|
|
return rootDirectory;
|
|
}
|
|
|
|
std::string TestName::checkParentDirectoriesForFile(const std::string& file)
|
|
{
|
|
auto newFileName = directoryPrefix + file;
|
|
|
|
if (!FileUtils::fileExists(newFileName))
|
|
{
|
|
// If the build system is Ninja, try looking several levels higher...
|
|
std::string backOne = ".." + SystemUtils::getDirectorySeparator();
|
|
std::string prefix;
|
|
for (int i = 0; i != 10; i++)
|
|
{
|
|
prefix += backOne;
|
|
auto candidateName = prefix + file;
|
|
if (FileUtils::fileExists(candidateName))
|
|
{
|
|
directoryPrefix = prefix;
|
|
return candidateName;
|
|
}
|
|
}
|
|
}
|
|
return newFileName;
|
|
}
|
|
|
|
bool TestName::registerRootDirectoryFromMainFile(const std::string& file)
|
|
{
|
|
std::cout << "TestName::registerRootDirectoryFromMainFile from __FILE__ " << file
|
|
<< '\n';
|
|
|
|
if (file.empty())
|
|
{
|
|
throw std::runtime_error("Cannot register an empty path as root directory");
|
|
}
|
|
|
|
std::string adjustedPath = file;
|
|
std::cout << "TestName::registerRootDirectoryFromMainFile found parent "
|
|
<< adjustedPath << '\n';
|
|
|
|
rootDirectoryStorage() = FileUtils::getDirectory(adjustedPath);
|
|
std::cout << "TestName::registerRootDirectoryFromMainFile result "
|
|
<< rootDirectoryStorage() << '\n';
|
|
|
|
return true;
|
|
}
|
|
|
|
std::string TestName::getRootDirectory()
|
|
{
|
|
if (!rootDirectoryStorage().empty())
|
|
{
|
|
return rootDirectoryStorage();
|
|
}
|
|
else
|
|
{
|
|
throw std::runtime_error(HelpMessages::getUnconfiguredRootDirectory());
|
|
}
|
|
}
|
|
|
|
std::string TestName::handleBoostQuirks() const
|
|
{
|
|
return "";
|
|
}
|
|
|
|
void TestName::checkBuildConfiguration(const std::string& fileName)
|
|
{
|
|
if (checkBuildConfig_ && !FileUtils::fileExists(fileName))
|
|
{
|
|
throw std::runtime_error(getMisconfiguredBuildHelp(fileName));
|
|
}
|
|
}
|
|
|
|
std::string TestName::getMisconfiguredBuildHelp(const std::string& fileName)
|
|
{
|
|
return "\n\n" + HelpMessages::getMisconfiguredBuildHelp(fileName) + "\n\n";
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getTestName() const
|
|
{
|
|
std::stringstream ext;
|
|
auto test = getCurrentTest();
|
|
for (size_t i = 0; i < test.sections.size(); i++)
|
|
{
|
|
if (0 < i)
|
|
{
|
|
ext << ".";
|
|
}
|
|
ext << test.sections[i];
|
|
}
|
|
|
|
return convertToFileName(ext.str());
|
|
}
|
|
|
|
std::string ApprovalTestNamer::convertToFileName(const std::string& fileName)
|
|
{
|
|
return FileNameSanitizerFactory::currentSanitizer(fileName);
|
|
}
|
|
|
|
TestName& ApprovalTestNamer::getCurrentTest()
|
|
{
|
|
try
|
|
{
|
|
return currentTest();
|
|
}
|
|
catch (const std::runtime_error&)
|
|
{
|
|
std::string helpMessage = getMisconfiguredMainHelp();
|
|
throw std::runtime_error(helpMessage);
|
|
}
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getMisconfiguredMainHelp()
|
|
{
|
|
return "\n\n" + HelpMessages::getMisconfiguredMainHelp() + "\n\n";
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getFileName() const
|
|
{
|
|
return getSourceFileName();
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getSourceFileName() const
|
|
{
|
|
auto file = getCurrentTest().getFileName();
|
|
auto start = file.rfind(SystemUtils::getDirectorySeparator()) + 1;
|
|
auto end = file.rfind('.');
|
|
auto fileName = file.substr(start, end - start);
|
|
return convertToFileName(fileName);
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getTestSourceDirectory() const
|
|
{
|
|
auto file = getCurrentTest().getFileName();
|
|
return FileUtils::getDirectory(file);
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getRelativeTestSourceDirectory() const
|
|
{
|
|
// We are using the original directory - as obtained from __FILE__,
|
|
// as this seems to be consistent for relative paths, regardless of
|
|
// Ninja __FILE__ quirks
|
|
auto originalDir =
|
|
FileUtils::getDirectory(getCurrentTest().getOriginalFileName());
|
|
originalDir =
|
|
StringUtils::replaceAll(originalDir, TestName::getRootDirectory(), "");
|
|
return originalDir;
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getApprovalsSubdirectory() const
|
|
{
|
|
std::string sub_directory;
|
|
if (!testConfiguration().subdirectory.empty())
|
|
{
|
|
sub_directory =
|
|
testConfiguration().subdirectory + SystemUtils::getDirectorySeparator();
|
|
}
|
|
return sub_directory;
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getDirectory() const
|
|
{
|
|
std::string directory = getTestSourceDirectory();
|
|
std::string sub_directory = getApprovalsSubdirectory();
|
|
directory += sub_directory;
|
|
SystemUtils::ensureDirectoryExists(directory);
|
|
return directory;
|
|
}
|
|
|
|
TestName& ApprovalTestNamer::currentTest(TestName* value)
|
|
{
|
|
static TestName* staticValue;
|
|
if (value != nullptr)
|
|
{
|
|
staticValue = value;
|
|
}
|
|
if (staticValue == nullptr)
|
|
{
|
|
throw std::runtime_error("The variable in currentTest() is not initialised");
|
|
}
|
|
return *staticValue;
|
|
}
|
|
|
|
TestConfiguration& ApprovalTestNamer::testConfiguration()
|
|
{
|
|
static TestConfiguration configuration;
|
|
return configuration;
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getApprovedFile(std::string extensionWithDot) const
|
|
{
|
|
|
|
return getFullFileName(".approved", extensionWithDot);
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getReceivedFile(std::string extensionWithDot) const
|
|
{
|
|
|
|
return getFullFileName(".received", extensionWithDot);
|
|
}
|
|
|
|
std::string ApprovalTestNamer::getOutputFileBaseName() const
|
|
{
|
|
return getSourceFileName() + "." + getTestName();
|
|
}
|
|
|
|
std::string
|
|
ApprovalTestNamer::getFullFileName(const std::string& approved,
|
|
const std::string& extensionWithDot) const
|
|
{
|
|
std::stringstream ext;
|
|
ext << getDirectory() << getOutputFileBaseName() << approved << extensionWithDot;
|
|
return ext.str();
|
|
}
|
|
|
|
bool ApprovalTestNamer::setCheckBuildConfig(bool enabled)
|
|
{
|
|
auto previous = TestName::checkBuildConfig_;
|
|
TestName::checkBuildConfig_ = enabled;
|
|
return previous;
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultNamerDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
DefaultNamerDisposer::DefaultNamerDisposer(NamerCreator namerCreator)
|
|
{
|
|
previous_result = DefaultNamerFactory::getDefaultNamer();
|
|
DefaultNamerFactory::setDefaultNamer(std::move(namerCreator));
|
|
}
|
|
|
|
DefaultNamerDisposer::~DefaultNamerDisposer()
|
|
{
|
|
DefaultNamerFactory::setDefaultNamer(previous_result);
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultNamerFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
NamerCreator& DefaultNamerFactory::defaultNamer()
|
|
{
|
|
static NamerCreator namer = []() {
|
|
return std::make_shared<ApprovalTestNamer>();
|
|
};
|
|
return namer;
|
|
}
|
|
|
|
NamerCreator DefaultNamerFactory::getDefaultNamer()
|
|
{
|
|
return defaultNamer();
|
|
}
|
|
|
|
void DefaultNamerFactory::setDefaultNamer(NamerCreator namer)
|
|
{
|
|
defaultNamer() = std::move(namer);
|
|
}
|
|
}
|
|
|
|
// ******************** From: ExistingFileNamer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
ExistingFileNamer::ExistingFileNamer(std::string filePath_, const Options& options)
|
|
: filePath(std::move(filePath_)), options_(options)
|
|
{
|
|
}
|
|
|
|
ExistingFileNamer::ExistingFileNamer(const ExistingFileNamer& x)
|
|
: filePath(x.filePath), options_(x.options_)
|
|
{
|
|
}
|
|
|
|
ExistingFileNamer::ExistingFileNamer(ExistingFileNamer&& x) noexcept
|
|
: filePath(std::move(x.filePath)), options_(x.options_)
|
|
{
|
|
}
|
|
|
|
std::string ExistingFileNamer::getApprovedFile(std::string extensionWithDot) const
|
|
{
|
|
return options_.getNamer()->getApprovedFile(extensionWithDot);
|
|
}
|
|
|
|
std::string ExistingFileNamer::getReceivedFile(std::string) const
|
|
{
|
|
return filePath;
|
|
}
|
|
}
|
|
|
|
// ******************** From: FileNameSanitizerDisposer.cpp
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
FileNameSanitizerDisposer::FileNameSanitizerDisposer(FileNameSanitizer sanitizer)
|
|
{
|
|
previous_result = std::move(FileNameSanitizerFactory::currentSanitizer);
|
|
FileNameSanitizerFactory::currentSanitizer = std::move(sanitizer);
|
|
}
|
|
|
|
FileNameSanitizerDisposer::~FileNameSanitizerDisposer()
|
|
{
|
|
FileNameSanitizerFactory::currentSanitizer = std::move(previous_result);
|
|
}
|
|
}
|
|
|
|
// ******************** From: FileNameSanitizerFactory.cpp
|
|
#include <sstream>
|
|
namespace ApprovalTests
|
|
{
|
|
bool FileNameSanitizerFactory::isForbidden(char c)
|
|
{
|
|
static std::string forbiddenChars("\\/:?\"<>|' ");
|
|
return std::string::npos != forbiddenChars.find(c);
|
|
}
|
|
|
|
std::string FileNameSanitizerFactory::defaultSanitizer(std::string fileName)
|
|
{
|
|
std::stringstream result;
|
|
for (auto ch : fileName)
|
|
{
|
|
if (!isForbidden(ch))
|
|
{
|
|
result << ch;
|
|
}
|
|
else
|
|
{
|
|
result << "_";
|
|
}
|
|
}
|
|
return result.str();
|
|
}
|
|
|
|
FileNameSanitizer FileNameSanitizerFactory::currentSanitizer =
|
|
FileNameSanitizerFactory::defaultSanitizer;
|
|
|
|
}
|
|
|
|
// ******************** From: HelpMessages.cpp
|
|
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
std::string HelpMessages::getMisconfiguredBuildHelp(const std::string& fileName)
|
|
{
|
|
std::string helpMessage = R"(* Welcome to Approval Tests.
|
|
*
|
|
* There seems to be a problem with your build configuration.
|
|
* We cannot find the test source file at:
|
|
* [fileName]
|
|
*
|
|
* For details on how to fix this, please visit:
|
|
* https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredBuild.md
|
|
*
|
|
* For advanced users only:
|
|
* If you believe you have reached this message in error, you can bypass
|
|
* the check by calling ApprovalTestNamer::setCheckBuildConfig(false);
|
|
)";
|
|
return StringUtils::replaceAll(
|
|
topAndTailHelpMessage(helpMessage), "[fileName]", fileName);
|
|
}
|
|
|
|
std::string HelpMessages::getMisconfiguredMainHelp()
|
|
{
|
|
std::string helpMessage = R"(* Welcome to Approval Tests.
|
|
*
|
|
* You have forgotten to configure your test framework for Approval Tests.
|
|
*
|
|
* To do this in Catch, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_CATCH
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* To do this in Google Test, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_GOOGLETEST
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* To do this in doctest, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_DOCTEST
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* To do this in Boost.Test, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_BOOSTTEST
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* To do this in CppUTest, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_CPPUTEST
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* To do this in [Boost].UT, add the following to your main.cpp:
|
|
*
|
|
* #define APPROVALS_UT
|
|
* #include "ApprovalTests.hpp"
|
|
*
|
|
* For more information, please visit:
|
|
* https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/TroubleshootingMisconfiguredMain.md
|
|
)";
|
|
return topAndTailHelpMessage(helpMessage);
|
|
}
|
|
|
|
std::string HelpMessages::getUnconfiguredRootDirectory()
|
|
{
|
|
std::string helpMessage = R"(* Hello from Approval Tests.
|
|
*
|
|
* It looks like your program is calling some code that requires knowledge of
|
|
* the location of the main() in your source tree.
|
|
*
|
|
* To do this, add the following to your main.cpp file:
|
|
*
|
|
* APPROVAL_TESTS_REGISTER_MAIN_DIRECTORY
|
|
*
|
|
* Currently, this is only required if you are using TemplatedCustomNamer's
|
|
* {RelativeTestSourceDirectory}.
|
|
)";
|
|
return topAndTailHelpMessage(helpMessage);
|
|
}
|
|
|
|
std::string
|
|
HelpMessages::getUnknownEnvVarReporterHelp(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns)
|
|
{
|
|
std::string helpMessage =
|
|
R"(* The environment variable [envVarName] contains the value
|
|
* [selected]
|
|
*
|
|
* This reporter is not recognised.
|
|
*
|
|
* Please unset the environment value, or change it to refer to one of the
|
|
* known reporters:
|
|
*
|
|
[known]*
|
|
* For more information, see:
|
|
* https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/how_tos/SelectReporterWithEnvironmentVariable.md
|
|
)";
|
|
|
|
return envVarErrorMessage(envVarName, selected, knowns, helpMessage);
|
|
}
|
|
|
|
std::string
|
|
HelpMessages::getInvalidEnvVarReporterHelp(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns)
|
|
{
|
|
std::string helpMessage =
|
|
R"(* The environment variable [envVarName] contains the value
|
|
* [selected]
|
|
*
|
|
* This reporter is recognised, but cannot be found on this machine.
|
|
*
|
|
* Please unset the environment value, or change it to refer to a working
|
|
* reporter:
|
|
*
|
|
[known]*
|
|
* For more information, see:
|
|
* https://github.com/approvals/ApprovalTests.cpp/blob/master/doc/how_tos/SelectReporterWithEnvironmentVariable.md
|
|
)";
|
|
|
|
return envVarErrorMessage(envVarName, selected, knowns, helpMessage);
|
|
}
|
|
|
|
std::string HelpMessages::envVarErrorMessage(const std::string& envVarName,
|
|
const std::string& selected,
|
|
const std::vector<std::string>& knowns,
|
|
std::string& helpMessage)
|
|
{
|
|
std::stringstream ss;
|
|
for (auto& known : knowns)
|
|
{
|
|
ss << "* " << known << '\n';
|
|
}
|
|
helpMessage = StringUtils::replaceAll(helpMessage, "[selected]", selected);
|
|
helpMessage = StringUtils::replaceAll(helpMessage, "[envVarName]", envVarName);
|
|
return topAndTailHelpMessage(
|
|
StringUtils::replaceAll(helpMessage, "[known]", ss.str()));
|
|
}
|
|
|
|
std::string HelpMessages::topAndTailHelpMessage(const std::string& message)
|
|
{
|
|
const std::string lineBreak =
|
|
"**************************************************************"
|
|
"***************";
|
|
const std::string lineBuffer =
|
|
"* "
|
|
" *\n";
|
|
return lineBreak + '\n' + lineBuffer + message + lineBuffer + lineBreak;
|
|
}
|
|
}
|
|
|
|
// ******************** From: NamerFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
SectionNameDisposer
|
|
NamerFactory::appendToOutputFilename(const std::string& sectionName)
|
|
{
|
|
return SectionNameDisposer(ApprovalTestNamer::currentTest(), sectionName);
|
|
}
|
|
}
|
|
|
|
// ******************** From: SectionNameDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
SectionNameDisposer::SectionNameDisposer(TestName& currentTest_,
|
|
const std::string& scope_name)
|
|
: currentTest(currentTest_)
|
|
{
|
|
// Add extra section to output filename, to allow multiple files
|
|
// to verified from a single test:
|
|
currentTest_.sections.push_back(scope_name);
|
|
}
|
|
|
|
SectionNameDisposer::~SectionNameDisposer()
|
|
{
|
|
// Remove the extra section we added in the constructor
|
|
currentTest.sections.pop_back();
|
|
}
|
|
}
|
|
|
|
// ******************** From: SeparateApprovedAndReceivedDirectoriesNamer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string separateDirectoryPath()
|
|
{
|
|
// clang-format off
|
|
auto path = "{TestSourceDirectory}/{ApprovalsSubdirectory}/{ApprovedOrReceived}/{TestFileName}.{TestCaseName}.{FileExtension}";
|
|
// clang-format on
|
|
return path;
|
|
}
|
|
|
|
SeparateApprovedAndReceivedDirectoriesNamer::
|
|
SeparateApprovedAndReceivedDirectoriesNamer()
|
|
: TemplatedCustomNamer(separateDirectoryPath())
|
|
{
|
|
}
|
|
|
|
DefaultNamerDisposer SeparateApprovedAndReceivedDirectoriesNamer::useAsDefaultNamer()
|
|
{
|
|
return Approvals::useAsDefaultNamer([]() {
|
|
return std::make_shared<SeparateApprovedAndReceivedDirectoriesNamer>();
|
|
});
|
|
}
|
|
}
|
|
|
|
// ******************** From: SubdirectoryDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
SubdirectoryDisposer::SubdirectoryDisposer(std::string subdirectory)
|
|
{
|
|
previous_result = ApprovalTestNamer::testConfiguration().subdirectory;
|
|
ApprovalTestNamer::testConfiguration().subdirectory = std::move(subdirectory);
|
|
}
|
|
|
|
SubdirectoryDisposer::~SubdirectoryDisposer()
|
|
{
|
|
ApprovalTestNamer::testConfiguration().subdirectory = previous_result;
|
|
}
|
|
}
|
|
|
|
// ******************** From: TemplatedCustomNamer.cpp
|
|
|
|
#include <functional>
|
|
#include <utility>
|
|
|
|
namespace
|
|
{
|
|
std::string replaceIfContains(std::string input,
|
|
std::string pattern,
|
|
std::function<std::string(void)> replacer)
|
|
{
|
|
if (!ApprovalTests::StringUtils::contains(input, pattern))
|
|
{
|
|
return input;
|
|
}
|
|
return ApprovalTests::StringUtils::replaceAll(input, pattern, replacer());
|
|
}
|
|
}
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
TemplatedCustomNamer::TemplatedCustomNamer(std::string templateString)
|
|
: template_(std::move(templateString))
|
|
{
|
|
if (!StringUtils::contains(template_, "{ApprovedOrReceived}"))
|
|
{
|
|
throw std::runtime_error(
|
|
"Template must contain `{ApprovedOrReceived}` or the received and "
|
|
"approved files will not be unique.\n"
|
|
"Template: " +
|
|
template_);
|
|
}
|
|
}
|
|
|
|
Path TemplatedCustomNamer::constructFromTemplate(
|
|
const std::string& extensionWithDot,
|
|
const std::string& approvedOrReceivedReplacement) const
|
|
{
|
|
std::string result = template_;
|
|
auto testSourceDirectory = "{TestSourceDirectory}";
|
|
auto relativeTestSourceDirectory = "{RelativeTestSourceDirectory}";
|
|
auto approvalsSubdirectory = "{ApprovalsSubdirectory}";
|
|
auto testFileName = "{TestFileName}";
|
|
auto testCaseName = "{TestCaseName}";
|
|
auto approvedOrReceived = "{ApprovedOrReceived}";
|
|
auto fileExtension = "{FileExtension}";
|
|
|
|
using namespace ApprovalTests;
|
|
|
|
// clang-format off
|
|
result = replaceIfContains(result, fileExtension, [&](){return extensionWithDot.substr(1);});
|
|
result = replaceIfContains(result, approvalsSubdirectory, [&](){return namer_.getApprovalsSubdirectory();});
|
|
result = replaceIfContains(result, relativeTestSourceDirectory, [&](){return namer_.getRelativeTestSourceDirectory();});
|
|
result = replaceIfContains(result, testFileName, [&](){return namer_.getSourceFileName();});
|
|
result = replaceIfContains(result, testCaseName, [&](){return namer_.getTestName();});
|
|
result = replaceIfContains(result, testSourceDirectory, [&](){return namer_.getTestSourceDirectory();});
|
|
result = replaceIfContains(result, approvedOrReceived, [&](){return approvedOrReceivedReplacement;});
|
|
// clang-format on
|
|
|
|
// Convert to native directory separators:
|
|
return Path(result);
|
|
}
|
|
|
|
std::string TemplatedCustomNamer::getApprovedFile(std::string extensionWithDot) const
|
|
{
|
|
return getApprovedFileAsPath(extensionWithDot).toString();
|
|
}
|
|
|
|
std::string TemplatedCustomNamer::getReceivedFile(std::string extensionWithDot) const
|
|
{
|
|
return getReceivedFileAsPath(extensionWithDot).toString();
|
|
}
|
|
|
|
Path TemplatedCustomNamer::getApprovedFileAsPath(std::string extensionWithDot) const
|
|
{
|
|
return constructFromTemplate(extensionWithDot, "approved");
|
|
}
|
|
|
|
Path TemplatedCustomNamer::getReceivedFileAsPath(std::string extensionWithDot) const
|
|
{
|
|
return constructFromTemplate(extensionWithDot, "received");
|
|
}
|
|
|
|
std::shared_ptr<TemplatedCustomNamer>
|
|
TemplatedCustomNamer::create(std::string templateString)
|
|
{
|
|
return std::make_shared<TemplatedCustomNamer>(templateString);
|
|
}
|
|
|
|
DefaultNamerDisposer
|
|
TemplatedCustomNamer::useAsDefaultNamer(std::string templateString)
|
|
{
|
|
return Approvals::useAsDefaultNamer([=]() { return create(templateString); });
|
|
}
|
|
}
|
|
|
|
// ******************** From: AutoApproveIfMissingReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool AutoApproveIfMissingReporter::report(std::string received,
|
|
std::string approved) const
|
|
{
|
|
if (FileUtils::fileExists(approved))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return AutoApproveReporter().report(received, approved);
|
|
}
|
|
}
|
|
|
|
// ******************** From: AutoApproveReporter.cpp
|
|
|
|
#include <iostream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool AutoApproveReporter::report(std::string received, std::string approved) const
|
|
{
|
|
std::cout << "file " << approved
|
|
<< " automatically approved - next run should succeed\n";
|
|
FileUtilsSystemSpecific::copyFile(received, approved);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ******************** From: BlockingReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
BlockingReporter::BlockingReporter(std::shared_ptr<Blocker> blocker_)
|
|
: blocker(std::move(blocker_))
|
|
{
|
|
}
|
|
|
|
std::shared_ptr<BlockingReporter>
|
|
BlockingReporter::onMachineNamed(const std::string& machineName)
|
|
{
|
|
auto machineBlocker =
|
|
std::make_shared<MachineBlocker>(MachineBlocker::onMachineNamed(machineName));
|
|
return std::make_shared<BlockingReporter>(machineBlocker);
|
|
}
|
|
|
|
std::shared_ptr<BlockingReporter>
|
|
BlockingReporter::onMachinesNotNamed(const std::string& machineName)
|
|
{
|
|
auto machineBlocker = std::make_shared<MachineBlocker>(
|
|
MachineBlocker::onMachinesNotNamed(machineName));
|
|
return std::make_shared<BlockingReporter>(machineBlocker);
|
|
}
|
|
|
|
bool BlockingReporter::report(std::string, std::string) const
|
|
{
|
|
return blocker->isBlockingOnThisMachine();
|
|
}
|
|
}
|
|
|
|
// ******************** From: CIBuildOnlyReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
CIBuildOnlyReporter::CIBuildOnlyReporter(std::shared_ptr<Reporter> reporter)
|
|
: m_reporter(reporter)
|
|
{
|
|
}
|
|
|
|
bool CIBuildOnlyReporter::report(std::string received, std::string approved) const
|
|
{
|
|
if (!isRunningUnderCI())
|
|
{
|
|
return false;
|
|
}
|
|
m_reporter->report(received, approved);
|
|
// Return true regardless of whether our report succeeded or not,
|
|
// so that no later reporters run.
|
|
return true;
|
|
}
|
|
|
|
bool CIBuildOnlyReporter::isRunningUnderCI()
|
|
{
|
|
/*
|
|
auto AppVeyor = {"CI", "APPVEYOR"}; // https://www.appveyor.com/docs/environment-variables/
|
|
auto AzurePipelines = {"TF_BUILD"}; // https://docs.microsoft.com/en-us/azure/devops/pipelines/build/variables?view=azure-devops&viewFallbackFrom=vsts&tabs=yaml
|
|
auto GitHubActions = {"GITHUB_ACTIONS"}; // https://help.github.com/en/actions/configuring-and-managing-workflows/using-environment-variables
|
|
auto GoCD = {"GO_SERVER_URL"}: // https://docs.gocd.org/current/faq/dev_use_current_revision_in_build.html
|
|
auto Jenkins = {"JENKINS_URL"}: // https://wiki.jenkins.io/display/JENKINS/Building+a+software+project
|
|
auto TeamCity = {"TEAMCITY_VERSION"}; // https://confluence.jetbrains.com/display/TCD18/Predefined+Build+Parameters
|
|
auto Travis = {"CI", "TRAVIS", "CONTINUOUS_INTEGRATION"}; // https://docs.travis-ci.com/user/environment-variables/#default-environment-variables
|
|
auto environmentVariablesForCI = combine({
|
|
AppVeyor,
|
|
AzurePipelines,
|
|
GitHubActions,
|
|
GoCD
|
|
Jenkins,
|
|
TeamCity,
|
|
Travis,
|
|
});
|
|
*/
|
|
auto environmentVariablesForCI = {
|
|
"CI",
|
|
"CONTINUOUS_INTEGRATION",
|
|
"GITHUB_ACTIONS",
|
|
"GO_SERVER_URL",
|
|
"JENKINS_URL",
|
|
"TEAMCITY_VERSION",
|
|
"TF_BUILD"
|
|
};
|
|
for (const auto& variable : environmentVariablesForCI)
|
|
{
|
|
if (!SystemUtils::safeGetEnv(variable).empty())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ******************** From: CIBuildOnlyReporterUtils.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
FrontLoadedReporterDisposer CIBuildOnlyReporterUtils::useAsFrontLoadedReporter(
|
|
const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
return Approvals::useAsFrontLoadedReporter(
|
|
std::make_shared<ApprovalTests::CIBuildOnlyReporter>(reporter));
|
|
}
|
|
}
|
|
|
|
// ******************** From: ClipboardReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string ClipboardReporter::getCommandLineFor(const std::string& received,
|
|
const std::string& approved,
|
|
bool isWindows)
|
|
{
|
|
if (isWindows)
|
|
{
|
|
return std::string("move /Y ") + "\"" + received + "\" \"" + approved + "\"";
|
|
}
|
|
else
|
|
{
|
|
return std::string("mv ") + "\"" + received + "\" \"" + approved + "\"";
|
|
}
|
|
}
|
|
|
|
bool ClipboardReporter::report(std::string received, std::string approved) const
|
|
{
|
|
copyToClipboard(
|
|
getCommandLineFor(received, approved, SystemUtils::isWindowsOs()));
|
|
return true;
|
|
}
|
|
|
|
void ClipboardReporter::copyToClipboard(const std::string& newClipboard)
|
|
{
|
|
/*
|
|
This will probably not work on Linux.
|
|
|
|
From https://stackoverflow.com/a/750466/104370
|
|
In case of X, yes, there's xclip (and others). xclip -selection c will send data to the clipboard that
|
|
works with Ctrl-C, Ctrl-V in most applications.
|
|
|
|
If you're trying to talk to the Mac OS X clipboard, there's pbcopy.
|
|
|
|
If you're in Linux terminal mode (no X) then maybe you need to look into gpm.
|
|
|
|
There's also GNU screen which has a clipboard. To put stuff in there, look at the screen command "readreg".
|
|
|
|
Under Windows/cygwin, use /dev/clipboard or clip for newer versions of Windows (at least Windows 10).
|
|
*/
|
|
|
|
std::string clipboardCommand;
|
|
if (SystemUtils::isWindowsOs())
|
|
{
|
|
clipboardCommand = "clip";
|
|
}
|
|
else if (SystemUtils::isMacOs())
|
|
{
|
|
clipboardCommand = "pbcopy";
|
|
}
|
|
else
|
|
{
|
|
clipboardCommand = "pbclip";
|
|
}
|
|
auto cmd = std::string("echo ") + newClipboard + " | " + clipboardCommand;
|
|
SystemUtils::runSystemCommandOrThrow(cmd);
|
|
}
|
|
}
|
|
|
|
// ******************** From: CombinationReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
CombinationReporter::CombinationReporter(const std::vector<Reporter*>& theReporters)
|
|
{
|
|
for (auto r : theReporters)
|
|
{
|
|
reporters.push_back(std::unique_ptr<Reporter>(r));
|
|
}
|
|
}
|
|
|
|
bool CombinationReporter::report(std::string received, std::string approved) const
|
|
{
|
|
bool result = false;
|
|
for (auto& r : reporters)
|
|
{
|
|
result |= r->report(received, approved);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// ******************** From: CommandReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string CommandReporter::assembleFullCommand(const std::string& received,
|
|
const std::string& approved) const
|
|
{
|
|
auto convertedCommand = '"' + converter->convertProgramForCygwin(cmd) + '"';
|
|
auto convertedReceived =
|
|
'"' + converter->convertFileArgumentForCygwin(received) + '"';
|
|
auto convertedApproved =
|
|
'"' + converter->convertFileArgumentForCygwin(approved) + '"';
|
|
|
|
std::string args;
|
|
args = StringUtils::replaceAll(
|
|
arguments, DiffInfo::receivedFileTemplate(), convertedReceived);
|
|
args = StringUtils::replaceAll(
|
|
args, DiffInfo::approvedFileTemplate(), convertedApproved);
|
|
|
|
return convertedCommand + ' ' + args;
|
|
}
|
|
|
|
CommandReporter::CommandReporter(std::string command, CommandLauncher* launcher)
|
|
: cmd(std::move(command)), l(launcher)
|
|
{
|
|
checkForCygwin();
|
|
}
|
|
|
|
CommandReporter::CommandReporter(std::string command,
|
|
std::string args,
|
|
CommandLauncher* launcher)
|
|
: cmd(std::move(command)), arguments(std::move(args)), l(launcher)
|
|
{
|
|
checkForCygwin();
|
|
}
|
|
|
|
bool CommandReporter::exists(const std::string& command)
|
|
{
|
|
bool foundByWhich = false;
|
|
if (!SystemUtils::isWindowsOs())
|
|
{
|
|
std::string which = "which " + command + " > /dev/null 2>&1";
|
|
int result = system(which.c_str());
|
|
foundByWhich = (result == 0);
|
|
}
|
|
return foundByWhich || FileUtils::fileExists(command);
|
|
}
|
|
|
|
bool CommandReporter::report(std::string received, std::string approved) const
|
|
{
|
|
if (!exists(cmd))
|
|
{
|
|
return false;
|
|
}
|
|
FileUtils::ensureFileExists(approved);
|
|
return l->launch(assembleFullCommand(received, approved));
|
|
}
|
|
|
|
std::string CommandReporter::getCommandLine(const std::string& received,
|
|
const std::string& approved) const
|
|
{
|
|
return l->getCommandLine(assembleFullCommand(received, approved));
|
|
}
|
|
|
|
void CommandReporter::checkForCygwin()
|
|
{
|
|
useCygwinConversions(SystemUtils::isCygwin());
|
|
}
|
|
|
|
void CommandReporter::useCygwinConversions(bool useCygwin)
|
|
{
|
|
if (useCygwin)
|
|
{
|
|
converter = std::make_shared<ConvertForCygwin>();
|
|
}
|
|
else
|
|
{
|
|
converter = std::make_shared<DoNothing>();
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: ConvertForCygwin.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string ConvertForCygwin::convertProgramForCygwin(const std::string& filePath)
|
|
{
|
|
return "$(cygpath '" + filePath + "')";
|
|
}
|
|
|
|
std::string
|
|
ConvertForCygwin::convertFileArgumentForCygwin(const std::string& filePath)
|
|
{
|
|
return "$(cygpath -aw '" + filePath + "')";
|
|
}
|
|
|
|
std::string DoNothing::convertProgramForCygwin(const std::string& filePath)
|
|
{
|
|
return filePath;
|
|
}
|
|
|
|
std::string DoNothing::convertFileArgumentForCygwin(const std::string& filePath)
|
|
{
|
|
return filePath;
|
|
}
|
|
}
|
|
|
|
// ******************** From: CrossPlatformReporters.cpp
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace CrossPlatform
|
|
{
|
|
VisualStudioCodeReporter::VisualStudioCodeReporter()
|
|
: GenericDiffReporter(DiffPrograms::CrossPlatform::VS_CODE())
|
|
{
|
|
}
|
|
|
|
CrossPlatformDiffReporter::CrossPlatformDiffReporter()
|
|
: FirstWorkingReporter({
|
|
new VisualStudioCodeReporter(),
|
|
})
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: CustomReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::shared_ptr<GenericDiffReporter> CustomReporter::create(std::string path,
|
|
Type type)
|
|
{
|
|
return create(std::move(path), DiffInfo::getDefaultArguments(), type);
|
|
}
|
|
|
|
std::shared_ptr<GenericDiffReporter>
|
|
CustomReporter::create(std::string path, std::string arguments, Type type)
|
|
{
|
|
DiffInfo info(std::move(path), std::move(arguments), type);
|
|
return std::make_shared<GenericDiffReporter>(info);
|
|
}
|
|
|
|
std::shared_ptr<GenericDiffReporter> CustomReporter::createForegroundReporter(
|
|
std::string path, Type type, bool allowNonZeroExitCodes)
|
|
{
|
|
return createForegroundReporter(std::move(path),
|
|
DiffInfo::getDefaultArguments(),
|
|
type,
|
|
allowNonZeroExitCodes);
|
|
}
|
|
|
|
std::shared_ptr<GenericDiffReporter> CustomReporter::createForegroundReporter(
|
|
std::string path, std::string arguments, Type type, bool allowNonZeroExitCodes)
|
|
{
|
|
DiffInfo info(std::move(path), std::move(arguments), type);
|
|
auto reporter = std::make_shared<GenericDiffReporter>(info);
|
|
reporter->launcher.setForeground(true);
|
|
reporter->launcher.setAllowNonZeroExitCodes(allowNonZeroExitCodes);
|
|
return reporter;
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultFrontLoadedReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
DefaultFrontLoadedReporter::DefaultFrontLoadedReporter()
|
|
: FirstWorkingReporter({new CIBuildOnlyReporter()})
|
|
{
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool DefaultReporter::report(std::string received, std::string approved) const
|
|
{
|
|
return DefaultReporterFactory::getDefaultReporter()->report(received, approved);
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultReporterDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
DefaultReporterDisposer::DefaultReporterDisposer(
|
|
const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
previous_result = DefaultReporterFactory::getDefaultReporter();
|
|
DefaultReporterFactory::setDefaultReporter(reporter);
|
|
}
|
|
|
|
DefaultReporterDisposer::~DefaultReporterDisposer()
|
|
{
|
|
DefaultReporterFactory::setDefaultReporter(previous_result);
|
|
}
|
|
}
|
|
|
|
// ******************** From: DefaultReporterFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::shared_ptr<Reporter>& DefaultReporterFactory::defaultReporter()
|
|
{
|
|
static std::shared_ptr<Reporter> reporter = std::make_shared<DiffReporter>();
|
|
return reporter;
|
|
}
|
|
|
|
std::shared_ptr<Reporter> DefaultReporterFactory::getDefaultReporter()
|
|
{
|
|
return defaultReporter();
|
|
}
|
|
|
|
void
|
|
DefaultReporterFactory::setDefaultReporter(const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
defaultReporter() = reporter;
|
|
}
|
|
}
|
|
|
|
// ******************** From: DiffInfo.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string DiffInfo::receivedFileTemplate()
|
|
{
|
|
return "{Received}";
|
|
}
|
|
|
|
std::string DiffInfo::approvedFileTemplate()
|
|
{
|
|
return "{Approved}";
|
|
}
|
|
|
|
std::string DiffInfo::programFileTemplate()
|
|
{
|
|
return "{ProgramFiles}";
|
|
}
|
|
|
|
std::string DiffInfo::getDefaultArguments()
|
|
{
|
|
return receivedFileTemplate() + ' ' + approvedFileTemplate();
|
|
}
|
|
|
|
DiffInfo::DiffInfo(std::string program_, Type type_)
|
|
: program(std::move(program_)), arguments(getDefaultArguments()), type(type_)
|
|
{
|
|
}
|
|
|
|
DiffInfo::DiffInfo(std::string program_, std::string arguments_, Type type_)
|
|
: program(std::move(program_)), arguments(std::move(arguments_)), type(type_)
|
|
{
|
|
}
|
|
|
|
std::vector<std::string> DiffInfo::getProgramFileLocations()
|
|
{
|
|
std::vector<std::string> possibleWindowsPaths;
|
|
const std::vector<const char*> envVars = {
|
|
"ProgramFiles", "ProgramW6432", "ProgramFiles(x86)"};
|
|
|
|
for (const auto& envVar : envVars)
|
|
{
|
|
std::string envVarValue = SystemUtils::safeGetEnv(envVar);
|
|
if (!envVarValue.empty())
|
|
{
|
|
envVarValue += '\\';
|
|
possibleWindowsPaths.push_back(envVarValue);
|
|
}
|
|
}
|
|
return possibleWindowsPaths;
|
|
}
|
|
|
|
std::string DiffInfo::getProgramForOs() const
|
|
{
|
|
std::string result = program;
|
|
if (result.rfind(programFileTemplate(), 0) == 0)
|
|
{
|
|
std::vector<std::string> possibleWindowsPaths = getProgramFileLocations();
|
|
for (const auto& path : possibleWindowsPaths)
|
|
{
|
|
auto result1 =
|
|
StringUtils::replaceAll(result, programFileTemplate(), path);
|
|
if (FileUtils::fileExists(result1))
|
|
{
|
|
return result1;
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// ******************** From: DiffPrograms.cpp
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
#define APPROVAL_TESTS_MACROS_ENTRY(name, defaultValue) \
|
|
DiffInfo name() \
|
|
{ \
|
|
return defaultValue; \
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace DiffPrograms
|
|
{
|
|
|
|
namespace CrossPlatform
|
|
{
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
VS_CODE, DiffInfo("code", "-d {Received} {Approved}", Type::TEXT))
|
|
}
|
|
|
|
namespace Mac
|
|
{
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
DIFF_MERGE,
|
|
DiffInfo("/Applications/DiffMerge.app/Contents/MacOS/DiffMerge",
|
|
"{Received} {Approved} -nosplash",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
ARAXIS_MERGE,
|
|
DiffInfo("/Applications/Araxis Merge.app/Contents/Utilities/compare",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
BEYOND_COMPARE,
|
|
DiffInfo("/Applications/Beyond Compare.app/Contents/MacOS/bcomp",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
KALEIDOSCOPE,
|
|
DiffInfo("/Applications/Kaleidoscope.app/Contents/MacOS/ksdiff",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE,
|
|
DiffInfo("/Applications/Sublime "
|
|
"Merge.app/Contents/SharedSupport/bin/smerge",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
KDIFF3,
|
|
DiffInfo("/Applications/kdiff3.app/Contents/MacOS/kdiff3",
|
|
"{Received} {Approved} -m -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
P4MERGE,
|
|
DiffInfo("/Applications/p4merge.app/Contents/MacOS/p4merge",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
TK_DIFF,
|
|
DiffInfo("/Applications/TkDiff.app/Contents/MacOS/tkdiff", Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
VS_CODE,
|
|
DiffInfo("/Applications/Visual Studio "
|
|
"Code.app/Contents/Resources/app/bin/code",
|
|
"-d {Received} {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(CLION,
|
|
DiffInfo("clion",
|
|
"nosplash diff {Received} {Approved}",
|
|
Type::TEXT))
|
|
}
|
|
|
|
namespace Linux
|
|
{
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE_SNAP,
|
|
DiffInfo("/snap/bin/sublime-merge",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE_FLATPAK,
|
|
DiffInfo("/var/lib/flatpak/exports/bin/com.sublimemerge.App",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE_REPOSITORY_PACKAGE,
|
|
DiffInfo("smerge",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE_DIRECT_DOWNLOAD,
|
|
DiffInfo("/opt/sublime_merge/sublime_merge",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
// More ideas available from: https://www.tecmint.com/best-linux-file-diff-tools-comparison/
|
|
APPROVAL_TESTS_MACROS_ENTRY(KDIFF3,
|
|
DiffInfo("kdiff3",
|
|
"{Received} {Approved} -m -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(MELD, DiffInfo("meld", Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(BEYOND_COMPARE,
|
|
DiffInfo("bcompare", Type::TEXT_AND_IMAGE))
|
|
}
|
|
|
|
namespace Windows
|
|
{
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
BEYOND_COMPARE_3,
|
|
DiffInfo("{ProgramFiles}Beyond Compare 3\\BCompare.exe",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
BEYOND_COMPARE_4,
|
|
DiffInfo("{ProgramFiles}Beyond Compare 4\\BCompare.exe",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
TORTOISE_IMAGE_DIFF,
|
|
DiffInfo("{ProgramFiles}TortoiseSVN\\bin\\TortoiseIDiff.exe",
|
|
"/left:{Received} /right:{Approved}",
|
|
Type::IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
TORTOISE_TEXT_DIFF,
|
|
DiffInfo("{ProgramFiles}TortoiseSVN\\bin\\TortoiseMerge.exe", Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
TORTOISE_GIT_IMAGE_DIFF,
|
|
DiffInfo("{ProgramFiles}TortoiseGit\\bin\\TortoiseGitIDiff.exe",
|
|
"/left:{Received} /right:{Approved}",
|
|
Type::IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
TORTOISE_GIT_TEXT_DIFF,
|
|
DiffInfo("{ProgramFiles}TortoiseGit\\bin\\TortoiseGitMerge.exe",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(WIN_MERGE_REPORTER,
|
|
DiffInfo("{ProgramFiles}WinMerge\\WinMergeU.exe",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
ARAXIS_MERGE,
|
|
DiffInfo("{ProgramFiles}Araxis\\Araxis Merge\\Compare.exe",
|
|
Type::TEXT_AND_IMAGE))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
CODE_COMPARE,
|
|
DiffInfo("{ProgramFiles}Devart\\Code Compare\\CodeCompare.exe",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
SUBLIME_MERGE,
|
|
DiffInfo("{ProgramFiles}Sublime Merge\\smerge.exe",
|
|
"mergetool --no-wait {Received} {Approved} -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(KDIFF3,
|
|
DiffInfo("{ProgramFiles}KDiff3\\bin\\kdiff3.exe",
|
|
"{Received} {Approved} -m -o {Approved}",
|
|
Type::TEXT))
|
|
|
|
APPROVAL_TESTS_MACROS_ENTRY(
|
|
VS_CODE,
|
|
DiffInfo("{ProgramFiles}Microsoft VS Code\\Code.exe",
|
|
"-d {Received} {Approved}",
|
|
Type::TEXT))
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
// ******************** From: DiffReporter.cpp
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
DiffReporter::DiffReporter()
|
|
: FirstWorkingReporter({new EnvironmentVariableReporter(),
|
|
new Mac::MacDiffReporter(),
|
|
new Linux::LinuxDiffReporter(),
|
|
new Windows::WindowsDiffReporter(),
|
|
new CrossPlatform::CrossPlatformDiffReporter()})
|
|
{
|
|
}
|
|
}
|
|
|
|
// ******************** From: EnvironmentVariableReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool EnvironmentVariableReporter::report(std::string received,
|
|
std::string approved) const
|
|
{
|
|
// Get the env var
|
|
const auto envVarName = environmentVariableName();
|
|
const auto envVar = SystemUtils::safeGetEnv(envVarName.c_str());
|
|
return report(envVar, received, approved);
|
|
}
|
|
|
|
bool EnvironmentVariableReporter::report(const std::string& envVar,
|
|
const std::string& received,
|
|
const std::string& approved) const
|
|
{
|
|
if (envVar.empty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
auto reporter = factory.createReporter(envVar);
|
|
auto known = factory.allSupportedReporterNames();
|
|
|
|
if (!reporter)
|
|
{
|
|
auto message = HelpMessages::getUnknownEnvVarReporterHelp(
|
|
EnvironmentVariableReporter::environmentVariableName(), envVar, known);
|
|
throw std::runtime_error(message);
|
|
}
|
|
|
|
auto reporter_worked = reporter->report(received, approved);
|
|
|
|
if (!reporter_worked)
|
|
{
|
|
auto message = HelpMessages::getInvalidEnvVarReporterHelp(
|
|
EnvironmentVariableReporter::environmentVariableName(), envVar, known);
|
|
throw std::runtime_error(message);
|
|
}
|
|
|
|
return reporter_worked;
|
|
}
|
|
|
|
std::string EnvironmentVariableReporter::environmentVariableName()
|
|
{
|
|
return "APPROVAL_TESTS_USE_REPORTER";
|
|
}
|
|
}
|
|
|
|
// ******************** From: FirstWorkingReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
FirstWorkingReporter::FirstWorkingReporter(const std::vector<Reporter*>& theReporters)
|
|
{
|
|
for (auto r : theReporters)
|
|
{
|
|
reporters.push_back(std::shared_ptr<Reporter>(r));
|
|
}
|
|
}
|
|
|
|
FirstWorkingReporter::FirstWorkingReporter(
|
|
const std::vector<std::shared_ptr<Reporter>>& reporters_)
|
|
{
|
|
this->reporters = reporters_;
|
|
}
|
|
|
|
bool FirstWorkingReporter::report(std::string received, std::string approved) const
|
|
{
|
|
for (auto& r : reporters)
|
|
{
|
|
if (r->report(received, approved))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// ******************** From: FrontLoadedReporterDisposer.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
FrontLoadedReporterDisposer::FrontLoadedReporterDisposer(
|
|
const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
previous_result = FrontLoadedReporterFactory::getFrontLoadedReporter();
|
|
FrontLoadedReporterFactory::setFrontLoadedReporter(reporter);
|
|
}
|
|
|
|
FrontLoadedReporterDisposer::~FrontLoadedReporterDisposer()
|
|
{
|
|
FrontLoadedReporterFactory::setFrontLoadedReporter(previous_result);
|
|
}
|
|
}
|
|
|
|
// ******************** From: FrontLoadedReporterFactory.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::shared_ptr<Reporter>& FrontLoadedReporterFactory::frontLoadedReporter()
|
|
{
|
|
static std::shared_ptr<Reporter> reporter =
|
|
std::make_shared<DefaultFrontLoadedReporter>();
|
|
return reporter;
|
|
}
|
|
|
|
std::shared_ptr<Reporter> FrontLoadedReporterFactory::getFrontLoadedReporter()
|
|
{
|
|
return frontLoadedReporter();
|
|
}
|
|
|
|
void FrontLoadedReporterFactory::setFrontLoadedReporter(
|
|
const std::shared_ptr<Reporter>& reporter)
|
|
{
|
|
frontLoadedReporter() = reporter;
|
|
}
|
|
}
|
|
|
|
// ******************** From: GenericDiffReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
GenericDiffReporter::GenericDiffReporter(const std::string& program)
|
|
: CommandReporter(program, &launcher)
|
|
{
|
|
}
|
|
|
|
GenericDiffReporter::GenericDiffReporter(const DiffInfo& info)
|
|
: CommandReporter(info.getProgramForOs(), info.arguments, &launcher)
|
|
{
|
|
}
|
|
}
|
|
|
|
// ******************** From: LinuxReporters.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Linux
|
|
{
|
|
SublimeMergeSnapReporter::SublimeMergeSnapReporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_SNAP())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
SublimeMergeFlatpakReporter::SublimeMergeFlatpakReporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_FLATPAK())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
SublimeMergeRepositoryPackageReporter::SublimeMergeRepositoryPackageReporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_REPOSITORY_PACKAGE())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
SublimeMergeDirectDownloadReporter::SublimeMergeDirectDownloadReporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::SUBLIME_MERGE_DIRECT_DOWNLOAD())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
SublimeMergeReporter::SublimeMergeReporter()
|
|
: FirstWorkingReporter({new SublimeMergeSnapReporter(),
|
|
new SublimeMergeFlatpakReporter(),
|
|
new SublimeMergeRepositoryPackageReporter(),
|
|
new SublimeMergeDirectDownloadReporter()})
|
|
{
|
|
}
|
|
|
|
KDiff3Reporter::KDiff3Reporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::KDIFF3())
|
|
{
|
|
}
|
|
|
|
MeldReporter::MeldReporter() : GenericDiffReporter(DiffPrograms::Linux::MELD())
|
|
{
|
|
}
|
|
|
|
BeyondCompareReporter::BeyondCompareReporter()
|
|
: GenericDiffReporter(DiffPrograms::Linux::BEYOND_COMPARE())
|
|
{
|
|
}
|
|
|
|
LinuxDiffReporter::LinuxDiffReporter()
|
|
: FirstWorkingReporter({
|
|
new BeyondCompareReporter(),
|
|
new MeldReporter(),
|
|
new SublimeMergeReporter(),
|
|
new KDiff3Reporter()
|
|
})
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: MacReporters.cpp
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Mac
|
|
{
|
|
DiffMergeReporter::DiffMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::DIFF_MERGE())
|
|
{
|
|
}
|
|
|
|
AraxisMergeReporter::AraxisMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::ARAXIS_MERGE())
|
|
{
|
|
}
|
|
|
|
VisualStudioCodeReporter::VisualStudioCodeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::VS_CODE())
|
|
{
|
|
}
|
|
|
|
BeyondCompareReporter::BeyondCompareReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::BEYOND_COMPARE())
|
|
{
|
|
}
|
|
|
|
KaleidoscopeReporter::KaleidoscopeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::KALEIDOSCOPE())
|
|
{
|
|
}
|
|
|
|
SublimeMergeReporter::SublimeMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::SUBLIME_MERGE())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
KDiff3Reporter::KDiff3Reporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::KDIFF3())
|
|
{
|
|
}
|
|
|
|
P4MergeReporter::P4MergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::P4MERGE())
|
|
{
|
|
}
|
|
TkDiffReporter::TkDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::TK_DIFF())
|
|
{
|
|
}
|
|
|
|
CLionDiffReporter::CLionDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Mac::CLION())
|
|
{
|
|
}
|
|
|
|
MacDiffReporter::MacDiffReporter()
|
|
: FirstWorkingReporter({
|
|
new AraxisMergeReporter(),
|
|
new BeyondCompareReporter(),
|
|
new DiffMergeReporter(),
|
|
new KaleidoscopeReporter(),
|
|
new P4MergeReporter(),
|
|
new SublimeMergeReporter(),
|
|
new KDiff3Reporter(),
|
|
new TkDiffReporter(),
|
|
new VisualStudioCodeReporter(),
|
|
new CLionDiffReporter()
|
|
})
|
|
{
|
|
}
|
|
|
|
bool MacDiffReporter::report(std::string received, std::string approved) const
|
|
{
|
|
if (!SystemUtils::isMacOs())
|
|
{
|
|
return false;
|
|
}
|
|
return FirstWorkingReporter::report(received, approved);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: QuietReporter.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool QuietReporter::report(std::string, std::string) const
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// ******************** From: ReporterFactory.cpp
|
|
|
|
|
|
#include <map>
|
|
#include <functional>
|
|
|
|
#define APPROVAL_TESTS_REGISTER_REPORTER(name) \
|
|
map[#name] = []() { return std::unique_ptr<Reporter>(new name); }
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
std::string getOsPrefix()
|
|
{
|
|
if (SystemUtils::isMacOs())
|
|
{
|
|
return "Mac::";
|
|
}
|
|
|
|
if (SystemUtils::isWindowsOs())
|
|
{
|
|
return "Windows::";
|
|
}
|
|
|
|
return "Linux::";
|
|
}
|
|
|
|
ReporterFactory::ReporterFactory() : map(createMap())
|
|
{
|
|
}
|
|
|
|
ReporterFactory::Reporters ReporterFactory::createMap()
|
|
{
|
|
Reporters map;
|
|
|
|
APPROVAL_TESTS_REGISTER_REPORTER(AutoApproveIfMissingReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(AutoApproveReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(CIBuildOnlyReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(ClipboardReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(DefaultFrontLoadedReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(DefaultReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(DiffReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(EnvironmentVariableReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(QuietReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(TextDiffReporter);
|
|
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Linux::BeyondCompareReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Linux::MeldReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Linux::SublimeMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Linux::KDiff3Reporter);
|
|
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::AraxisMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::BeyondCompareReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::DiffMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::KaleidoscopeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::P4MergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::SublimeMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::KDiff3Reporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::TkDiffReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::VisualStudioCodeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Mac::CLionDiffReporter);
|
|
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::TortoiseDiffReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::TortoiseGitDiffReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::BeyondCompareReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::WinMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::AraxisMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::CodeCompareReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::SublimeMergeReporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::KDiff3Reporter);
|
|
APPROVAL_TESTS_REGISTER_REPORTER(Windows::VisualStudioCodeReporter);
|
|
|
|
APPROVAL_TESTS_REGISTER_REPORTER(CrossPlatform::VisualStudioCodeReporter);
|
|
|
|
return map;
|
|
}
|
|
|
|
std::unique_ptr<Reporter>
|
|
ReporterFactory::createReporter(const std::string& reporterName) const
|
|
{
|
|
auto osPrefix = getOsPrefix();
|
|
|
|
auto key = findReporterName(osPrefix, reporterName);
|
|
|
|
if (!key.empty())
|
|
{
|
|
return map.at(key)();
|
|
}
|
|
|
|
return std::unique_ptr<Reporter>();
|
|
}
|
|
|
|
std::vector<std::string> ReporterFactory::allSupportedReporterNames() const
|
|
{
|
|
std::vector<std::string> result;
|
|
|
|
for (auto& p : map)
|
|
{
|
|
result.push_back(p.first);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
std::string ReporterFactory::findReporterName(const std::string& osPrefix,
|
|
const std::string& reporterName) const
|
|
{
|
|
auto trimmedReporterName = StringUtils::trim(reporterName);
|
|
trimmedReporterName = StringUtils::toLower(trimmedReporterName);
|
|
|
|
std::vector<std::string> candidateNames = {
|
|
trimmedReporterName,
|
|
// Allow program names to be specified without Reporter suffix
|
|
trimmedReporterName + "reporter",
|
|
// Allow names without os namespace
|
|
StringUtils::toLower(osPrefix) + trimmedReporterName,
|
|
StringUtils::toLower(osPrefix) + trimmedReporterName + "reporter",
|
|
};
|
|
|
|
for (auto& candidateName : candidateNames)
|
|
{
|
|
auto iter = std::find_if(
|
|
map.begin(), map.end(), [&](const Reporters::value_type pair) {
|
|
return StringUtils::toLower(pair.first) == candidateName;
|
|
});
|
|
|
|
if (iter != map.end())
|
|
{
|
|
return iter->first;
|
|
}
|
|
}
|
|
|
|
return std::string{};
|
|
}
|
|
}
|
|
|
|
// ******************** From: TextDiffReporter.cpp
|
|
|
|
#include <iostream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
TextDiffReporter::TextDiffReporter() : TextDiffReporter(std::cout)
|
|
{
|
|
}
|
|
|
|
TextDiffReporter::TextDiffReporter(std::ostream& stream) : stream_(stream)
|
|
{
|
|
std::vector<std::shared_ptr<Reporter>> reporters = {
|
|
CustomReporter::createForegroundReporter("diff", Type::TEXT, true),
|
|
CustomReporter::createForegroundReporter(
|
|
"C:/Windows/System32/fc.exe", Type::TEXT_AND_IMAGE, true)};
|
|
m_reporter = std::unique_ptr<Reporter>(new FirstWorkingReporter(reporters));
|
|
}
|
|
|
|
bool TextDiffReporter::report(std::string received, std::string approved) const
|
|
{
|
|
stream_ << "Comparing files:" << std::endl;
|
|
stream_ << "received: " << received << std::endl;
|
|
stream_ << "approved: " << approved << std::endl;
|
|
const bool result = m_reporter->report(received, approved);
|
|
if (!result)
|
|
{
|
|
stream_ << "TextDiffReporter did not find a working diff "
|
|
"program\n\n";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// ******************** From: WindowsReporters.cpp
|
|
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Windows
|
|
{
|
|
VisualStudioCodeReporter::VisualStudioCodeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::VS_CODE())
|
|
{
|
|
}
|
|
|
|
// ----------------------- Beyond Compare ----------------------------------
|
|
BeyondCompare3Reporter::BeyondCompare3Reporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::BEYOND_COMPARE_3())
|
|
{
|
|
}
|
|
|
|
BeyondCompare4Reporter::BeyondCompare4Reporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::BEYOND_COMPARE_4())
|
|
{
|
|
}
|
|
|
|
BeyondCompareReporter::BeyondCompareReporter()
|
|
: FirstWorkingReporter(
|
|
{new BeyondCompare4Reporter(), new BeyondCompare3Reporter()})
|
|
{
|
|
}
|
|
|
|
// ----------------------- Tortoise SVN ------------------------------------
|
|
TortoiseImageDiffReporter::TortoiseImageDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::TORTOISE_IMAGE_DIFF())
|
|
{
|
|
}
|
|
|
|
TortoiseTextDiffReporter::TortoiseTextDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::TORTOISE_TEXT_DIFF())
|
|
{
|
|
}
|
|
|
|
TortoiseDiffReporter::TortoiseDiffReporter()
|
|
: FirstWorkingReporter(
|
|
{new TortoiseTextDiffReporter(), new TortoiseImageDiffReporter()})
|
|
{
|
|
}
|
|
|
|
// ----------------------- Tortoise Git ------------------------------------
|
|
TortoiseGitTextDiffReporter::TortoiseGitTextDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::TORTOISE_GIT_TEXT_DIFF())
|
|
{
|
|
}
|
|
|
|
TortoiseGitImageDiffReporter::TortoiseGitImageDiffReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::TORTOISE_GIT_IMAGE_DIFF())
|
|
{
|
|
}
|
|
|
|
TortoiseGitDiffReporter::TortoiseGitDiffReporter()
|
|
: FirstWorkingReporter(
|
|
{new TortoiseGitTextDiffReporter(), new TortoiseGitImageDiffReporter()})
|
|
{
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
WinMergeReporter::WinMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::WIN_MERGE_REPORTER())
|
|
{
|
|
}
|
|
|
|
AraxisMergeReporter::AraxisMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::ARAXIS_MERGE())
|
|
{
|
|
}
|
|
|
|
CodeCompareReporter::CodeCompareReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::CODE_COMPARE())
|
|
{
|
|
}
|
|
|
|
SublimeMergeReporter::SublimeMergeReporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::SUBLIME_MERGE())
|
|
{
|
|
launcher.setForeground(true);
|
|
}
|
|
|
|
KDiff3Reporter::KDiff3Reporter()
|
|
: GenericDiffReporter(DiffPrograms::Windows::KDIFF3())
|
|
{
|
|
}
|
|
|
|
WindowsDiffReporter::WindowsDiffReporter()
|
|
: FirstWorkingReporter({
|
|
new TortoiseDiffReporter(), // Note that this uses Tortoise SVN Diff
|
|
new TortoiseGitDiffReporter(),
|
|
new BeyondCompareReporter(),
|
|
new WinMergeReporter(),
|
|
new AraxisMergeReporter(),
|
|
new CodeCompareReporter(),
|
|
new SublimeMergeReporter(),
|
|
new KDiff3Reporter(),
|
|
new VisualStudioCodeReporter(),
|
|
})
|
|
{
|
|
}
|
|
|
|
bool WindowsDiffReporter::report(std::string received, std::string approved) const
|
|
{
|
|
if (!SystemUtils::isWindowsOs())
|
|
{
|
|
return false;
|
|
}
|
|
return FirstWorkingReporter::report(received, approved);
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: Scrubbers.cpp
|
|
|
|
#include <string>
|
|
#include <functional>
|
|
#include <regex>
|
|
#include <map>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
namespace Scrubbers
|
|
{
|
|
std::string doNothing(const std::string& input)
|
|
{
|
|
return input;
|
|
}
|
|
std::string scrubRegex(const std::string& input,
|
|
const std::regex& regex,
|
|
const RegexReplacer& replaceFunction)
|
|
{
|
|
std::string result;
|
|
std::string remainder = input;
|
|
std::smatch m;
|
|
while (std::regex_search(remainder, m, regex))
|
|
{
|
|
auto match = m[0];
|
|
auto original_matched_text = match.str();
|
|
auto replacement = replaceFunction(match);
|
|
result += std::string(m.prefix()) + replacement;
|
|
remainder = m.suffix();
|
|
}
|
|
result += remainder;
|
|
return result;
|
|
}
|
|
|
|
Scrubber createRegexScrubber(const std::regex& regexPattern,
|
|
const RegexReplacer& replacer)
|
|
{
|
|
return [=](const std::string& input) {
|
|
return scrubRegex(input, regexPattern, replacer);
|
|
};
|
|
}
|
|
|
|
Scrubber createRegexScrubber(const std::regex& regexPattern,
|
|
const std::string& replacementText)
|
|
{
|
|
return createRegexScrubber(
|
|
regexPattern, [=](const RegexMatch&) { return replacementText; });
|
|
}
|
|
|
|
Scrubber createRegexScrubber(const std::string& regexString,
|
|
const std::string& replacementText)
|
|
{
|
|
if (regexString.empty())
|
|
{
|
|
return doNothing;
|
|
}
|
|
return createRegexScrubber(std::regex(regexString), replacementText);
|
|
}
|
|
|
|
std::string scrubGuid(const std::string& input)
|
|
{
|
|
static const std::regex regex("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-["
|
|
"0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
|
|
|
|
int matchNumber = 0;
|
|
std::map<std::string, int> matchIndices;
|
|
return scrubRegex(input, regex, [&](const RegexMatch& m) {
|
|
auto guid_match = m.str();
|
|
|
|
if (matchIndices[guid_match] == 0)
|
|
{
|
|
matchIndices[guid_match] = ++matchNumber;
|
|
}
|
|
return "guid_" + std::to_string(matchIndices[guid_match]);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: DateUtils.cpp
|
|
|
|
#include <sstream>
|
|
#include <iomanip>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::tm
|
|
DateUtils::createTm(int year, int month, int day, int hour, int minute, int second)
|
|
{
|
|
std::tm timeinfo = tm();
|
|
timeinfo.tm_year = year - 1900;
|
|
timeinfo.tm_mon = month - 1;
|
|
timeinfo.tm_mday = day;
|
|
timeinfo.tm_hour = hour;
|
|
timeinfo.tm_min = minute;
|
|
timeinfo.tm_sec = second;
|
|
return timeinfo;
|
|
}
|
|
|
|
std::chrono::system_clock::time_point
|
|
DateUtils::createUtcDateTime(int year,
|
|
int month,
|
|
int day,
|
|
int hour,
|
|
int minute,
|
|
int second) // these are UTC values
|
|
{
|
|
tm timeinfo = createTm(year, month, day, hour, minute, second);
|
|
time_t tt = toUtc(timeinfo);
|
|
return std::chrono::system_clock::from_time_t(tt);
|
|
}
|
|
|
|
std::string DateUtils::toString(const std::chrono::system_clock::time_point& dateTime)
|
|
{
|
|
return toString(dateTime, "%a %Y-%m-%d %H:%M:%S UTC");
|
|
}
|
|
|
|
std::string DateUtils::toString(const std::chrono::system_clock::time_point& dateTime,
|
|
const std::string& format)
|
|
{
|
|
time_t tt = std::chrono::system_clock::to_time_t(dateTime);
|
|
tm tm_value = toUtc(tt);
|
|
|
|
return StringUtils::toString(std::put_time(&tm_value, format.c_str()));
|
|
}
|
|
|
|
time_t DateUtils::toUtc(std::tm& timeinfo)
|
|
{
|
|
#ifdef _WIN32
|
|
std::time_t tt = _mkgmtime(&timeinfo);
|
|
#else
|
|
time_t tt = timegm(&timeinfo);
|
|
#endif
|
|
return tt;
|
|
}
|
|
|
|
tm DateUtils::toUtc(time_t& tt)
|
|
{
|
|
#ifdef _MSC_VER // Visual Studio compiler
|
|
std::tm tm_value = {};
|
|
gmtime_s(&tm_value, &tt);
|
|
#else
|
|
tm tm_value = *gmtime(&tt);
|
|
#endif
|
|
return tm_value;
|
|
}
|
|
}
|
|
|
|
// ******************** From: EmptyFileCreatorByType.cpp
|
|
|
|
namespace
|
|
{
|
|
std::map<std::string, ApprovalTests::EmptyFileCreator>
|
|
defaultEmptyFileCreatorByTypeCreators()
|
|
{
|
|
std::map<std::string, ApprovalTests::EmptyFileCreator> creators;
|
|
ApprovalTests::EmptyFileCreator wibbleCreator = [](std::string fileName) {
|
|
ApprovalTests::StringWriter s("{}");
|
|
s.write(fileName);
|
|
};
|
|
creators[".json"] = wibbleCreator;
|
|
return creators;
|
|
}
|
|
}
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::map<std::string, ApprovalTests::EmptyFileCreator>
|
|
EmptyFileCreatorByType::creators_ = defaultEmptyFileCreatorByTypeCreators();
|
|
|
|
void EmptyFileCreatorByType::registerCreator(const std::string& extensionWithDot,
|
|
EmptyFileCreator creator)
|
|
{
|
|
creators_[extensionWithDot] = std::move(creator);
|
|
}
|
|
|
|
void EmptyFileCreatorByType::createFile(const std::string& fileName)
|
|
{
|
|
for (const auto& creator : creators_)
|
|
{
|
|
if (StringUtils::endsWith(fileName, creator.first))
|
|
{
|
|
creator.second(fileName);
|
|
return;
|
|
}
|
|
}
|
|
EmptyFileCreatorFactory::defaultCreator(fileName);
|
|
}
|
|
}
|
|
|
|
// ******************** From: EmptyFileCreatorDisposer.cpp
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
EmptyFileCreatorDisposer::EmptyFileCreatorDisposer(EmptyFileCreator creator)
|
|
{
|
|
previous_result = std::move(EmptyFileCreatorFactory::currentCreator);
|
|
EmptyFileCreatorFactory::currentCreator = std::move(creator);
|
|
}
|
|
|
|
EmptyFileCreatorDisposer::~EmptyFileCreatorDisposer()
|
|
{
|
|
EmptyFileCreatorFactory::currentCreator = std::move(previous_result);
|
|
}
|
|
}
|
|
|
|
// ******************** From: EmptyFileCreatorFactory.cpp
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
void EmptyFileCreatorFactory::defaultCreator(std::string fullFilePath)
|
|
{
|
|
StringWriter s("", "");
|
|
s.write(fullFilePath);
|
|
}
|
|
|
|
EmptyFileCreator EmptyFileCreatorFactory::currentCreator =
|
|
EmptyFileCreatorByType::createFile;
|
|
}
|
|
|
|
// ******************** From: ExceptionCollector.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
void ExceptionCollector::gather(std::function<void(void)> functionThatThrows)
|
|
{
|
|
try
|
|
{
|
|
functionThatThrows();
|
|
}
|
|
catch (const std::exception& e)
|
|
{
|
|
exceptionMessages.emplace_back(e.what());
|
|
}
|
|
}
|
|
|
|
ExceptionCollector::~ExceptionCollector()
|
|
{
|
|
if (!exceptionMessages.empty())
|
|
{
|
|
exceptionMessages.emplace_back("ERROR: Calling code forgot to call "
|
|
"exceptionCollector.release()");
|
|
}
|
|
release();
|
|
}
|
|
|
|
void ExceptionCollector::release()
|
|
{
|
|
if (!exceptionMessages.empty())
|
|
{
|
|
std::stringstream s;
|
|
s << exceptionMessages.size() << " exceptions were thrown:\n\n";
|
|
int count = 1;
|
|
for (const auto& error : exceptionMessages)
|
|
{
|
|
s << count++ << ") " << error << '\n';
|
|
}
|
|
exceptionMessages.clear();
|
|
throw std::runtime_error(s.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: FileUtils.cpp
|
|
|
|
#include <fstream>
|
|
#include <sys/stat.h>
|
|
#include <sstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool FileUtils::fileExists(const std::string& path)
|
|
{
|
|
struct stat info
|
|
{
|
|
};
|
|
return stat(path.c_str(), &info) == 0;
|
|
}
|
|
|
|
int FileUtils::fileSize(const std::string& path)
|
|
{
|
|
struct stat statbuf
|
|
{
|
|
};
|
|
int stat_ok = stat(path.c_str(), &statbuf);
|
|
|
|
if (stat_ok == -1)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
return int(statbuf.st_size);
|
|
}
|
|
|
|
EmptyFileCreatorDisposer FileUtils::useEmptyFileCreator(EmptyFileCreator creator)
|
|
{
|
|
return EmptyFileCreatorDisposer(creator);
|
|
}
|
|
|
|
void FileUtils::ensureFileExists(const std::string& fullFilePath)
|
|
{
|
|
if (!fileExists(fullFilePath))
|
|
{
|
|
EmptyFileCreatorFactory::currentCreator(fullFilePath);
|
|
}
|
|
}
|
|
|
|
std::string FileUtils::getDirectory(const std::string& filePath)
|
|
{
|
|
auto end = filePath.rfind(SystemUtils::getDirectorySeparator()) + 1;
|
|
auto directory = filePath.substr(0, end);
|
|
return directory;
|
|
}
|
|
|
|
std::string FileUtils::getExtensionWithDot(const std::string& filePath)
|
|
{
|
|
std::size_t found = filePath.find_last_of('.');
|
|
return filePath.substr(found);
|
|
}
|
|
|
|
std::string FileUtils::readFileThrowIfMissing(const std::string& fileName)
|
|
{
|
|
std::ifstream in(fileName.c_str(), std::ios_base::in);
|
|
if (!in)
|
|
{
|
|
throw std::runtime_error("File does not exist: " + fileName);
|
|
}
|
|
std::stringstream written;
|
|
written << in.rdbuf();
|
|
in.close();
|
|
|
|
std::string text = written.str();
|
|
return text;
|
|
}
|
|
|
|
std::string FileUtils::readFileReturnEmptyIfMissing(const std::string& fileName)
|
|
{
|
|
if (FileUtils::fileExists(fileName))
|
|
{
|
|
return readFileThrowIfMissing(fileName);
|
|
}
|
|
else
|
|
{
|
|
return std::string();
|
|
}
|
|
}
|
|
|
|
void FileUtils::writeToFile(const std::string& filePath, const std::string& content)
|
|
{
|
|
std::ofstream out(filePath.c_str(), std::ios::binary | std::ofstream::out);
|
|
if (!out)
|
|
{
|
|
throw std::runtime_error("Unable to write file: " + filePath);
|
|
}
|
|
out << content;
|
|
}
|
|
|
|
}
|
|
|
|
// ******************** From: FileUtilsSystemSpecific.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string FileUtilsSystemSpecific::getCommandLineForCopy(
|
|
const std::string& source, const std::string& destination, bool isWindows)
|
|
{
|
|
if (isWindows)
|
|
{
|
|
return std::string("copy /Y ") + "\"" + source + "\" \"" + destination + "\"";
|
|
}
|
|
else
|
|
{
|
|
return std::string("cp ") + "\"" + source + "\" \"" + destination + "\"";
|
|
}
|
|
}
|
|
|
|
void FileUtilsSystemSpecific::copyFile(const std::string& source,
|
|
const std::string& destination)
|
|
{
|
|
auto cmd = getCommandLineForCopy(source, destination, SystemUtils::isWindowsOs());
|
|
SystemUtils::runSystemCommandOrThrow(cmd);
|
|
}
|
|
}
|
|
|
|
// ******************** From: Grid.cpp
|
|
namespace ApprovalTests
|
|
{
|
|
std::string Grid::print(int width,
|
|
int height,
|
|
std::function<void(int, int, std::ostream&)> printCell)
|
|
{
|
|
std::stringstream s;
|
|
for (int y = 0; y < height; ++y)
|
|
{
|
|
for (int x = 0; x < width; ++x)
|
|
{
|
|
printCell(x, y, s);
|
|
}
|
|
s << '\n';
|
|
}
|
|
return s.str();
|
|
}
|
|
std::string Grid::print(int width, int height, std::string text)
|
|
{
|
|
return print(
|
|
width, height, [&](int /*x*/, int /*y*/, std::ostream& os) { os << text; });
|
|
}
|
|
}
|
|
|
|
// ******************** From: MachineBlocker.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
MachineBlocker::MachineBlocker(std::string machineName_, bool block_)
|
|
: machineName(std::move(machineName_)), block(block_)
|
|
{
|
|
}
|
|
|
|
MachineBlocker MachineBlocker::onMachineNamed(const std::string& machineName)
|
|
{
|
|
return MachineBlocker(machineName, true);
|
|
}
|
|
|
|
MachineBlocker MachineBlocker::onMachinesNotNamed(const std::string& machineName)
|
|
{
|
|
return MachineBlocker(machineName, false);
|
|
}
|
|
|
|
bool MachineBlocker::isBlockingOnThisMachine() const
|
|
{
|
|
const auto isMachine = (SystemUtils::getMachineName() == machineName);
|
|
return isMachine == block;
|
|
}
|
|
}
|
|
|
|
// ******************** From: MoreHelpMessages.cpp
|
|
|
|
#include <iostream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
void MoreHelpMessages::deprecatedFunctionCalled(const std::string& message,
|
|
const std::string& file,
|
|
int lineNumber)
|
|
{
|
|
std::cout << "\n***************** Deprecation Warning: ***************\n"
|
|
<< "*\n"
|
|
<< "* " << message << '\n'
|
|
<< "*\n"
|
|
<< "* Deprecated method:\n"
|
|
<< "* " << file << ":" << lineNumber << '\n'
|
|
<< "* Called from:\n"
|
|
<< "* " << ApprovalTestNamer::getCurrentTest().getFileName() << '\n'
|
|
<< "*\n"
|
|
<< "******************************************************\n\n";
|
|
}
|
|
}
|
|
|
|
// ******************** From: Path.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
|
|
Path::Path(const std::string& start) : path_(normalizeSeparators(start))
|
|
{
|
|
}
|
|
|
|
std::string Path::toString() const
|
|
{
|
|
return toString(separator_);
|
|
}
|
|
|
|
std::string Path::toString(const std::string& directoryPathSeparator) const
|
|
{
|
|
std::string path = removeRedundantDirectorySeparators(path_);
|
|
|
|
if (separator_ == directoryPathSeparator)
|
|
{
|
|
return path;
|
|
}
|
|
return StringUtils::replaceAll(path, separator_, directoryPathSeparator);
|
|
}
|
|
|
|
std::string Path::removeRedundantDirectorySeparators(std::string path) const
|
|
{
|
|
bool changed = true;
|
|
while (changed)
|
|
{
|
|
std::string reducePath = path;
|
|
reducePath = StringUtils::replaceAll(reducePath, "//", "/");
|
|
reducePath = StringUtils::replaceAll(reducePath, "\\\\", "\\");
|
|
changed = (reducePath != path);
|
|
path = reducePath;
|
|
};
|
|
return path;
|
|
}
|
|
|
|
Path Path::operator+(const std::string& addition) const
|
|
{
|
|
return Path(path_ + addition);
|
|
}
|
|
|
|
Path Path::operator/(const std::string& addition) const
|
|
{
|
|
auto first = path_;
|
|
if (StringUtils::endsWith(first, separator_))
|
|
{
|
|
first = first.substr(0, path_.size() - 1);
|
|
}
|
|
|
|
auto second = addition;
|
|
if (StringUtils::beginsWith(second, separator_))
|
|
{
|
|
second = second.substr(1);
|
|
}
|
|
|
|
return Path(first + separator_ + second);
|
|
}
|
|
|
|
Path Path::operator/(const Path addition) const
|
|
{
|
|
return *this / addition.path_;
|
|
}
|
|
|
|
std::string Path::normalizeSeparators(const std::string& path)
|
|
{
|
|
auto separator = SystemUtils::getDirectorySeparator();
|
|
auto otherSeparator = (separator == "/" ? "\\" : "/");
|
|
return StringUtils::replaceAll(path, otherSeparator, separator);
|
|
}
|
|
}
|
|
|
|
// ******************** From: StringUtils.cpp
|
|
|
|
#include <cctype>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string StringUtils::replaceAll(std::string inText,
|
|
const std::string& find,
|
|
const std::string& replaceWith)
|
|
{
|
|
size_t start_pos = 0;
|
|
while ((start_pos = inText.find(find, start_pos)) != std::string::npos)
|
|
{
|
|
inText.replace(start_pos, find.length(), replaceWith);
|
|
start_pos +=
|
|
replaceWith.length(); // Handles case where 'to' is a substring of 'from'
|
|
}
|
|
return inText;
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
bool StringUtils::contains(const std::string& inText, const std::string& find)
|
|
{
|
|
return inText.find(find, 0) != std::string::npos;
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string StringUtils::toLower(std::string inText)
|
|
{
|
|
std::string copy(inText);
|
|
std::transform(inText.begin(), inText.end(), copy.begin(), [](char c) {
|
|
return static_cast<char>(tolower(c));
|
|
});
|
|
return copy;
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
bool StringUtils::beginsWith(std::string value, std::string beginning)
|
|
{
|
|
if (value.size() < beginning.size())
|
|
{
|
|
return false;
|
|
}
|
|
return std::equal(beginning.begin(), beginning.end(), value.begin());
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
bool StringUtils::endsWith(std::string value, std::string ending)
|
|
{
|
|
if (value.size() < ending.size())
|
|
{
|
|
return false;
|
|
}
|
|
return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string StringUtils::leftTrim(std::string s)
|
|
{
|
|
|
|
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
|
|
return !std::isspace(ch);
|
|
}));
|
|
return s;
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string StringUtils::rightTrim(std::string s)
|
|
{
|
|
s.erase(std::find_if(s.rbegin(),
|
|
s.rend(),
|
|
[](unsigned char ch) { return !std::isspace(ch); })
|
|
.base(),
|
|
s.end());
|
|
return s;
|
|
}
|
|
|
|
APPROVAL_TESTS_NO_DISCARD
|
|
std::string StringUtils::trim(std::string s)
|
|
{
|
|
s = leftTrim(s);
|
|
s = rightTrim(s);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
// ******************** From: SystemUtils.cpp
|
|
|
|
#include <sys/stat.h>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
bool SystemUtils::isWindowsOs()
|
|
{
|
|
#ifdef _WIN32
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool SystemUtils::isCygwin()
|
|
{
|
|
#ifdef __CYGWIN__
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool SystemUtils::isMacOs()
|
|
{
|
|
#ifdef __APPLE__
|
|
return true;
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
std::string SystemUtils::getDirectorySeparator()
|
|
{
|
|
return isWindowsOs() ? "\\" : "/";
|
|
}
|
|
|
|
std::string SystemUtils::checkFilenameCase(const std::string& fullPath)
|
|
{
|
|
if (!isWindowsOs() || !FileUtils::fileExists(fullPath))
|
|
{
|
|
return fullPath;
|
|
}
|
|
#ifdef _WIN32
|
|
|
|
_finddata_t findFileData;
|
|
auto hFind = _findfirst(fullPath.c_str(), &findFileData);
|
|
|
|
if (hFind != -1)
|
|
{
|
|
const std::string fixedFilename = findFileData.name;
|
|
const std::string fixedPath = StringUtils::replaceAll(
|
|
fullPath, StringUtils::toLower(fixedFilename), fixedFilename);
|
|
_findclose(hFind);
|
|
return fixedPath;
|
|
}
|
|
|
|
#endif
|
|
return fullPath;
|
|
}
|
|
|
|
std::string SystemUtils::safeGetEnvForWindows(const char* name)
|
|
{
|
|
APPROVAL_TESTS_UNUSED(name);
|
|
#ifdef _WIN32
|
|
// We use getenv_s on Windows, as use of getenv there gives:
|
|
// warning C4996: 'getenv': This function or variable may be unsafe. Consider using _dupenv_s instead.
|
|
// To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.
|
|
|
|
size_t size;
|
|
getenv_s(&size, nullptr, 0, name);
|
|
|
|
if (size != 0)
|
|
{
|
|
std::string result;
|
|
result.resize(size);
|
|
getenv_s(&size, &*result.begin(), size, name);
|
|
result.pop_back();
|
|
return result;
|
|
}
|
|
#endif
|
|
return std::string();
|
|
}
|
|
|
|
std::string SystemUtils::safeGetEnvForNonWindows(const char* name)
|
|
{
|
|
APPROVAL_TESTS_UNUSED(name);
|
|
char* p = nullptr;
|
|
#ifndef _WIN32
|
|
p = getenv(name);
|
|
#endif
|
|
return (p != nullptr) ? p : std::string();
|
|
}
|
|
|
|
std::string SystemUtils::safeGetEnv(const char* name)
|
|
{
|
|
return isWindowsOs() ? safeGetEnvForWindows(name) : safeGetEnvForNonWindows(name);
|
|
}
|
|
|
|
std::string SystemUtils::getMachineName()
|
|
{
|
|
auto name = safeGetEnv("COMPUTERNAME");
|
|
if (!name.empty())
|
|
{
|
|
return name;
|
|
}
|
|
|
|
name = safeGetEnv("HOSTNAME");
|
|
if (!name.empty())
|
|
{
|
|
return name;
|
|
}
|
|
|
|
return "Unknown Computer";
|
|
}
|
|
|
|
void SystemUtils::makeDirectoryForWindows(const std::string& directory)
|
|
{
|
|
APPROVAL_TESTS_UNUSED(directory);
|
|
#ifdef _WIN32
|
|
int nError = _mkdir(directory.c_str());
|
|
if (nError != 0)
|
|
{
|
|
std::string helpMessage =
|
|
std::string("Unable to create directory: ") + directory;
|
|
throw std::runtime_error(helpMessage);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SystemUtils::makeDirectoryForNonWindows(const std::string& directory)
|
|
{
|
|
APPROVAL_TESTS_UNUSED(directory);
|
|
#ifndef _WIN32
|
|
mode_t nMode = 0733; // UNIX style permissions
|
|
int nError = mkdir(directory.c_str(), nMode);
|
|
if (nError != 0)
|
|
{
|
|
std::string helpMessage =
|
|
std::string("Unable to create directory: ") + directory;
|
|
throw std::runtime_error(helpMessage);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
void SystemUtils::makeDirectory(const std::string& directory)
|
|
{
|
|
makeDirectoryForWindows(directory);
|
|
makeDirectoryForNonWindows(directory);
|
|
}
|
|
|
|
void SystemUtils::ensureDirectoryExists(const std::string& fullDirectoryPath)
|
|
{
|
|
if (!FileUtils::fileExists(fullDirectoryPath))
|
|
{
|
|
makeDirectory(fullDirectoryPath);
|
|
}
|
|
}
|
|
|
|
void SystemUtils::ensureParentDirectoryExists(const std::string& fullFilePath)
|
|
{
|
|
const std::string parentDirectory = FileUtils::getDirectory(fullFilePath);
|
|
if (!parentDirectory.empty())
|
|
{
|
|
SystemUtils::ensureDirectoryExists(parentDirectory);
|
|
}
|
|
}
|
|
|
|
void SystemUtils::runSystemCommandOrThrow(const std::string& command,
|
|
bool allowNonZeroExitCodes)
|
|
{
|
|
int exitCode = system(command.c_str());
|
|
|
|
if (!allowNonZeroExitCodes && exitCode != 0)
|
|
{
|
|
throw std::runtime_error(command + ": failed with exit code " +
|
|
std::to_string(exitCode));
|
|
}
|
|
}
|
|
}
|
|
|
|
// ******************** From: ExistingFile.cpp
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
std::string ExistingFile::scrub(std::string fileName, const Options& options)
|
|
{
|
|
if (options.isUsingDefaultScrubber())
|
|
{
|
|
return fileName;
|
|
}
|
|
auto content = FileUtils::readFileThrowIfMissing(fileName);
|
|
const auto scrubbedContent = options.scrub(content);
|
|
if (content == scrubbedContent)
|
|
{
|
|
deleteScrubbedFile = false;
|
|
return fileName;
|
|
}
|
|
else
|
|
{
|
|
std::size_t found = fileName.find_last_of('.');
|
|
auto fileExtension = fileName.substr(found);
|
|
std::string baseName = fileName.substr(0, found);
|
|
|
|
auto newFileName = baseName + ".scrubbed.received" + fileExtension;
|
|
FileUtils::writeToFile(newFileName, scrubbedContent);
|
|
deleteScrubbedFile = true;
|
|
return newFileName;
|
|
}
|
|
}
|
|
|
|
ExistingFile::ExistingFile(std::string filePath_, const Options& options)
|
|
: options_(options)
|
|
{
|
|
filePath = scrub(filePath_, options);
|
|
}
|
|
|
|
std::string ExistingFile::getFileExtensionWithDot() const
|
|
{
|
|
return FileUtils::getExtensionWithDot(filePath);
|
|
}
|
|
|
|
void ExistingFile::write(std::string) const
|
|
{
|
|
// do nothing
|
|
}
|
|
|
|
void ExistingFile::cleanUpReceived(std::string receivedPath) const
|
|
{
|
|
if (deleteScrubbedFile && (receivedPath == filePath))
|
|
{
|
|
remove(receivedPath.c_str());
|
|
}
|
|
}
|
|
|
|
ExistingFileNamer ExistingFile::getNamer()
|
|
{
|
|
return ExistingFileNamer(filePath, options_);
|
|
}
|
|
}
|
|
|
|
// ******************** From: StringWriter.cpp
|
|
|
|
#include <utility>
|
|
#include <fstream>
|
|
|
|
namespace ApprovalTests
|
|
{
|
|
StringWriter::StringWriter(std::string contents, std::string fileExtensionWithDot)
|
|
: s(std::move(contents)), ext(std::move(fileExtensionWithDot))
|
|
{
|
|
}
|
|
|
|
std::string StringWriter::getFileExtensionWithDot() const
|
|
{
|
|
return ext;
|
|
}
|
|
|
|
void StringWriter::write(std::string path) const
|
|
{
|
|
std::ofstream out(path.c_str(), std::ofstream::out);
|
|
if (!out)
|
|
{
|
|
throw std::runtime_error("Unable to write file: " + path);
|
|
}
|
|
this->Write(out);
|
|
out.close();
|
|
}
|
|
|
|
void StringWriter::Write(std::ostream& out) const
|
|
{
|
|
out << s << "\n";
|
|
}
|
|
|
|
void StringWriter::cleanUpReceived(std::string receivedPath) const
|
|
{
|
|
remove(receivedPath.c_str());
|
|
}
|
|
}
|
|
#endif // APPROVAL_TESTS_INCLUDE_CPPS
|
|
|
|
#endif // APPROVAL_TESTS_CPP_APPROVALS_HPP
|