diff --git a/docs/command-line/generated/mull-runner-cli-options.rst b/docs/command-line/generated/mull-runner-cli-options.rst index 7797e5cce..d631198f5 100644 --- a/docs/command-line/generated/mull-runner-cli-options.rst +++ b/docs/command-line/generated/mull-runner-cli-options.rst @@ -32,8 +32,6 @@ --mutation-score-threshold If mutation score falls under this threshold, and allow-surviving is not enabled, an error result code is returned ---no-test-output Does not capture output from test runs - --no-mutant-output Does not capture output from mutant runs --no-output Combines -no-test-output and -no-mutant-output diff --git a/include/mull/Config/Configuration.h b/include/mull/Config/Configuration.h index 5ba49db4c..96a579e80 100644 --- a/include/mull/Config/Configuration.h +++ b/include/mull/Config/Configuration.h @@ -7,43 +7,19 @@ namespace mull { +#include "GeneratedConfiguration.h" + extern int MullDefaultTimeoutMilliseconds; class Diagnostics; -struct Configuration { +struct Configuration : public GeneratedConfiguration { std::string pathOnDisk; - bool debugEnabled; - bool quiet; - bool silent; - bool dryRunEnabled; - bool captureTestOutput; bool captureMutantOutput; - bool includeNotCovered; - bool junkDetectionDisabled; - unsigned timeout; - IDEDiagnosticsKind diagnostics; - - std::vector mutators; - std::vector ignoreMutators; - std::string executable; - std::string compilationDatabasePath; - std::vector compilerFlags; - - std::vector includePaths; - std::vector excludePaths; - - ParallelizationConfig parallelization; - - std::string gitDiffRef; - std::string gitProjectRoot; - - DebugConfig debug{}; - Configuration(); static std::string findConfig(Diagnostics &diagnostics); diff --git a/include/mull/Config/ConfigurationOptions.h b/include/mull/Config/ConfigurationOptions.h index 6a232692a..478807c25 100644 --- a/include/mull/Config/ConfigurationOptions.h +++ b/include/mull/Config/ConfigurationOptions.h @@ -2,27 +2,15 @@ namespace mull { -enum class IDEDiagnosticsKind { None, Survived, Killed, All }; +#include "GeneratedDebugConfig.h" +#include "GeneratedParallelizationConfig.h" -struct ParallelizationConfig { - unsigned workers; - unsigned executionWorkers; - ParallelizationConfig(); +struct ParallelizationConfig : public GeneratedParallelizationConfig { static ParallelizationConfig defaultConfig(); void normalize(); bool exceedsHardware(); }; -struct DebugConfig { - bool printIR = false; - bool printIRBefore = false; - bool printIRAfter = false; - bool printIRToFile = false; - bool traceMutants = false; - bool coverage = false; - bool gitDiff = false; - bool filters = false; - bool slowIRVerification = false; -}; +struct DebugConfig : public GeneratedDebugConfig {}; } // namespace mull diff --git a/include/mull/Config/config.json b/include/mull/Config/config.json new file mode 100644 index 000000000..d4de44c10 --- /dev/null +++ b/include/mull/Config/config.json @@ -0,0 +1,153 @@ +[ + { + "type": "struct", + "name": "ParallelizationConfig", + "description": "Controls parallelization", + "fields": [ + { + "name": "workers", + "type": "unsigned", + "description": "How many threads to use for mutation and execution" + }, + { + "name": "executionWorkers", + "type": "unsigned", + "description": "How many threads to use for execution" + } + ] + }, + { + "type": "struct", + "name": "DebugConfig", + "description": "Enables certain debug features", + "fields": [ + { + "name": "printIR", + "type": "bool", + "description": "Prints LLVM IR before/after mutations into stderr" + }, + { + "name": "printIRBefore", + "type": "bool", + "description": "Prints LLVM IR before mutations into stderr" + }, + { + "name": "printIRAfter", + "type": "bool", + "description": "Prints LLVM IR after mutations into stderr" + }, + { + "name": "printIRToFile", + "type": "bool", + "description": "Prints LLVM IR (before or after) into a file instead of stderr" + }, + { + "name": "traceMutants", + "type": "bool", + "description": "Adds debug `printf`s into generated LLVM IR around mutations" + }, + { + "name": "coverage", + "type": "bool", + "description": "Prints more debug output when `coverage` filter is enabled" + }, + { + "name": "gitDiff", + "type": "bool", + "description": "Prints more debug output when `gitDiff` filter is enabled" + }, + { + "name": "filters", + "type": "bool", + "description": "Prints more debug output for various filters" + }, + { + "name": "slowIRVerification", + "type": "bool", + "description": "Turns on LLVM IR verification after each mutation is applied" + } + ] + }, + { + "type": "struct", + "name": "Configuration", + "description": "Mull config", + "fields": [ + { + "name": "debugEnabled", + "type": "bool", + "description": "Turns on debug output" + }, + { + "name": "quiet", + "type": "bool", + "description": "Turns off any output except of warnings and errors" + }, + { + "name": "silent", + "type": "bool", + "description": "Turns off any output" + }, + { + "name": "includeNotCovered", + "type": "bool", + "description": "Controls whether `coverage` filter should skip mutants that are not covered. Disabled by default." + }, + { + "name": "junkDetectionDisabled", + "type": "bool", + "description": "Controls whether `junk detection` filter is enabled or not. Enabled by default." + }, + { + "name": "mutators", + "type": "std::vector", + "description": "Controls which mutators to turn on" + }, + { + "name": "ignoreMutators", + "type": "std::vector", + "description": "Controls which mutators to turn off" + }, + { + "name": "compilationDatabasePath", + "type": "std::string", + "description": "Path to the compilation database (compile_commands.json)" + }, + { + "name": "compilerFlags", + "type": "std::vector", + "description": "Additional compiler flags used for junk detection" + }, + { + "name": "includePaths", + "type": "std::vector", + "description": "Controls behavior of `file path` filter" + }, + { + "name": "excludePaths", + "type": "std::vector", + "description": "Controls behavior of `file path` filter" + }, + { + "name": "parallelization", + "type": "ParallelizationConfig", + "description": "Controls parallelization (both mutation and mutant execution)" + }, + { + "name": "gitDiffRef", + "type": "std::string", + "description": "Controls the git ref for `git diff` filter" + }, + { + "name": "gitProjectRoot", + "type": "std::string", + "description": "Controls the git repo location for `git diff` filter" + }, + { + "name": "debug", + "type": "DebugConfig", + "description": "Controls various debug options" + } + ] + } +] diff --git a/lib/Config/CMakeLists.txt b/lib/Config/CMakeLists.txt index 415157c75..8e75ee516 100644 --- a/lib/Config/CMakeLists.txt +++ b/lib/Config/CMakeLists.txt @@ -5,5 +5,23 @@ add_library(mull-configuration ) target_include_directories(mull-configuration PUBLIC ${CMAKE_SOURCE_DIR}/include) target_include_directories(mull-configuration PRIVATE ${LLVM_INCLUDE_DIRS}) + # FIXME: replace with target_compile_options() -set_target_properties(mull-configuration PROPERTIES COMPILE_FLAGS ${MULL_CXX_FLAGS}) \ No newline at end of file +set_target_properties(mull-configuration PROPERTIES COMPILE_FLAGS ${MULL_CXX_FLAGS}) + +add_custom_command( + OUTPUT + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedYamlMapping.cpp + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedConfiguration.h + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedDebugConfig.h + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedParallelizationConfig.h + COMMAND + python3 ${CMAKE_CURRENT_SOURCE_DIR}/config_gen.py ${CMAKE_SOURCE_DIR}/include/mull/Config/config.json ${CMAKE_CURRENT_BINARY_DIR} + DEPENDS ${CMAKE_SOURCE_DIR}/include/mull/Config/config.json ${CMAKE_CURRENT_SOURCE_DIR}/config_gen.py +) +target_include_directories(mull-configuration PUBLIC ${CMAKE_CURRENT_BINARY_DIR}) +add_custom_target(generate-config-sources DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/GeneratedYamlMapping.cpp + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedConfiguration.h + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedDebugConfig.h + ${CMAKE_CURRENT_BINARY_DIR}/GeneratedParallelizationConfig.h) +add_dependencies(mull-configuration generate-config-sources) diff --git a/lib/Config/Configuration.cpp b/lib/Config/Configuration.cpp index 9dc9caabd..a221b0f35 100644 --- a/lib/Config/Configuration.cpp +++ b/lib/Config/Configuration.cpp @@ -5,10 +5,8 @@ namespace mull { int MullDefaultTimeoutMilliseconds = 3000; Configuration::Configuration() - : pathOnDisk(), debugEnabled(false), quiet(true), silent(false), dryRunEnabled(false), - captureTestOutput(true), captureMutantOutput(true), includeNotCovered(false), - junkDetectionDisabled(false), timeout(MullDefaultTimeoutMilliseconds), - diagnostics(IDEDiagnosticsKind::None), - parallelization(ParallelizationConfig::defaultConfig()) {} + : pathOnDisk(), captureMutantOutput(true), timeout(MullDefaultTimeoutMilliseconds) { + this->parallelization = ParallelizationConfig::defaultConfig(); +} } // namespace mull diff --git a/lib/Config/ConfigurationOptions.cpp b/lib/Config/ConfigurationOptions.cpp index 306adf812..b50c31563 100644 --- a/lib/Config/ConfigurationOptions.cpp +++ b/lib/Config/ConfigurationOptions.cpp @@ -5,8 +5,6 @@ namespace mull { -ParallelizationConfig::ParallelizationConfig() : workers(0), executionWorkers(0) {} - void ParallelizationConfig::normalize() { unsigned defaultWorkers = std::max(std::thread::hardware_concurrency(), unsigned(1)); if (workers == 0) { diff --git a/lib/Config/ConfigurationParser.cpp b/lib/Config/ConfigurationParser.cpp index 53023d637..8ac21c3bc 100644 --- a/lib/Config/ConfigurationParser.cpp +++ b/lib/Config/ConfigurationParser.cpp @@ -8,58 +8,7 @@ using namespace mull; using namespace std::string_literals; -template <> struct llvm::yaml::ScalarEnumerationTraits { - static void enumeration(llvm::yaml::IO &io, IDEDiagnosticsKind &value) { - io.enumCase(value, "none", IDEDiagnosticsKind::None); - io.enumCase(value, "killed", IDEDiagnosticsKind::Killed); - io.enumCase(value, "survived", IDEDiagnosticsKind::Survived); - io.enumCase(value, "all", IDEDiagnosticsKind::All); - } -}; - -template <> struct llvm::yaml::MappingTraits { - static void mapping(llvm::yaml::IO &io, ParallelizationConfig &config) { - io.mapOptional("workers", config.workers); - io.mapOptional("executionWorkers", config.executionWorkers); - } -}; - -template <> struct llvm::yaml::MappingTraits { - static void mapping(llvm::yaml::IO &io, DebugConfig &config) { - io.mapOptional("printIR", config.printIR); - io.mapOptional("printIRAfter", config.printIRAfter); - io.mapOptional("printIRBefore", config.printIRBefore); - io.mapOptional("printIRToFile", config.printIRToFile); - io.mapOptional("traceMutants", config.traceMutants); - io.mapOptional("coverage", config.coverage); - io.mapOptional("gitDiff", config.gitDiff); - io.mapOptional("filters", config.filters); - io.mapOptional("slowIRVerification", config.slowIRVerification); - } -}; - -template <> struct llvm::yaml::MappingTraits { - static void mapping(llvm::yaml::IO &io, Configuration &config) { - io.mapOptional("debugEnabled", config.debugEnabled); - io.mapOptional("quiet", config.quiet); - io.mapOptional("silent", config.silent); - io.mapOptional("captureTestOutput", config.captureTestOutput); - io.mapOptional("captureMutantOutput", config.captureMutantOutput); - io.mapOptional("includeNotCovered", config.includeNotCovered); - io.mapOptional("timeout", config.timeout); - io.mapOptional("mutators", config.mutators); - io.mapOptional("ignoreMutators", config.ignoreMutators); - io.mapOptional("parallelization", config.parallelization); - io.mapOptional("compilationDatabasePath", config.compilationDatabasePath); - io.mapOptional("compilerFlags", config.compilerFlags); - io.mapOptional("junkDetectionDisabled", config.junkDetectionDisabled); - io.mapOptional("gitDiffRef", config.gitDiffRef); - io.mapOptional("gitProjectRoot", config.gitProjectRoot); - io.mapOptional("includePaths", config.includePaths); - io.mapOptional("excludePaths", config.excludePaths); - io.mapOptional("debug", config.debug); - } -}; +#include "GeneratedYamlMapping.cpp" std::string Configuration::findConfig(Diagnostics &diagnostics) { if (getenv("MULL_CONFIG") != nullptr) { diff --git a/lib/Config/config_gen.py b/lib/Config/config_gen.py new file mode 100755 index 000000000..efa1e6f36 --- /dev/null +++ b/lib/Config/config_gen.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 + +import json +import dataclasses +from dacite import from_dict +import io +import sys + + +@dataclasses.dataclass +class ConfigField: + type: str + name: str + description: str + + +@dataclasses.dataclass +class ConfigEntry: + type: str + name: str + description: str + fields: list[ConfigField] = dataclasses.field(default_factory=list) + + +def load_raw_config(path: str): + with open(path, "r") as f: + return json.loads(f.read()) + + +def dump_cpp(conf: ConfigEntry, stream): + stream.write(f"\n// {conf.description}\n") + stream.write(f"struct Generated{conf.name} {{\n") + for f in conf.fields: + stream.write(f" // {f.description}\n") + stream.write(f" {f.type} {f.name}{{}};\n") + stream.write("};\n\n") + + +def dump_yaml_mapping(conf: ConfigEntry, stream): + stream.write(f"template <> struct llvm::yaml::MappingTraits<{conf.name}> {{\n") + stream.write(f" static void mapping(llvm::yaml::IO &io, {conf.name} &config) {{\n") + for f in conf.fields: + stream.write(f' io.mapOptional("{f.name}", config.{f.name});\n') + stream.write(" }\n") + stream.write("};\n") + + +def generate_headers(configs: list[ConfigEntry], output_dir: str): + for conf in configs: + output_file = f"{output_dir}/Generated{conf.name}.h" + out = io.StringIO() + dump_cpp(conf, out) + with open(output_file, "w") as f: + f.write(out.getvalue()) + + +def generate_yaml_mapping(configs: list[ConfigEntry], output_dir: str): + output_file = f"{output_dir}/GeneratedYamlMapping.cpp" + out = io.StringIO() + for conf in configs: + dump_yaml_mapping(conf, out) + with open(output_file, "w") as f: + f.write(out.getvalue()) + + +config_source = sys.argv[1] +output_dir = sys.argv[2] + +raw_config = load_raw_config(config_source) +configs = [] +for conf in raw_config: + conf = from_dict(data_class=ConfigEntry, data=conf) + configs.append(conf) + + +generate_headers(configs, output_dir) +generate_yaml_mapping(configs, output_dir) diff --git a/requirements.txt b/requirements.txt index e659e36eb..983383e2e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ lit==18.1.4 filecheck==0.0.24 cloudsmith-cli==1.2.3 +dacite=1.8.1 diff --git a/tools/CLIOptions/CLIOptions.h b/tools/CLIOptions/CLIOptions.h index a48344d79..891923576 100644 --- a/tools/CLIOptions/CLIOptions.h +++ b/tools/CLIOptions/CLIOptions.h @@ -155,14 +155,6 @@ opt MutationScoreThreshold( \ init(100), \ cat(MullCategory)) \ -#define NoTestOutput_() \ -opt NoTestOutput( \ - "no-test-output", \ - desc("Does not capture output from test runs"), \ - Optional, \ - init(false), \ - cat(MullCategory)) \ - #define NoMutantOutput_() \ opt NoMutantOutput( \ "no-mutant-output", \ diff --git a/tools/mull-runner/mull-runner-cli.h b/tools/mull-runner/mull-runner-cli.h index 237875e2f..a5e080c1e 100644 --- a/tools/mull-runner/mull-runner-cli.h +++ b/tools/mull-runner/mull-runner-cli.h @@ -18,7 +18,6 @@ MutationScoreThreshold_(); Timeout_(); Workers_(); NoOutput_(); -NoTestOutput_(); NoMutantOutput_(); ReportName_(); ReportDirectory_(); @@ -50,7 +49,6 @@ void dumpCLIInterface(mull::Diagnostics &diagnostics) { &AllowSurvivingEnabled, &MutationScoreThreshold, - &NoTestOutput, &NoMutantOutput, &NoOutput, diff --git a/tools/mull-runner/mull-runner.cpp b/tools/mull-runner/mull-runner.cpp index 54e877f60..ff19cbe2e 100644 --- a/tools/mull-runner/mull-runner.cpp +++ b/tools/mull-runner/mull-runner.cpp @@ -106,9 +106,6 @@ int main(int argc, char **argv) { configuration.parallelization = mull::ParallelizationConfig::defaultConfig(); } - if (tool::NoTestOutput.getNumOccurrences() || tool::NoOutput.getNumOccurrences()) { - configuration.captureTestOutput = false; - } if (tool::NoMutantOutput.getNumOccurrences() || tool::NoOutput.getNumOccurrences()) { configuration.captureMutantOutput = false; }