1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.frontend.argparser; 11 12 import logger = std.experimental.logger; 13 14 public import dextool.plugin.mutate.type; 15 public import dextool.plugin.mutate.backend : Mutation; 16 17 @safe: 18 19 /// The mode the tool is operating in 20 enum ToolMode { 21 /// No mode set 22 none, 23 /// analyze for mutation points 24 analyzer, 25 /// center that can operate and control subcomponents 26 generate_mutant, 27 /// test mutation points with a test suite 28 test_mutants, 29 /// generate a report of the mutation points 30 report, 31 /// administrator interface for the mutation database 32 admin, 33 } 34 35 /// Extract and cleanup user input from the command line. 36 struct ArgParser { 37 import std.typecons : Nullable; 38 import std.conv : ConvException; 39 import std.getopt : GetoptResult, getopt, defaultGetoptPrinter; 40 import std.traits : EnumMembers; 41 import dextool.type : FileName; 42 43 struct Data { 44 string[] inFiles; 45 string[] cflags; 46 string[] compileDb; 47 48 string outputDirectory = "."; 49 string[] restrictDir; 50 51 string db = "dextool_mutate.sqlite3"; 52 string mutationTester; 53 string mutationCompile; 54 string mutationTestCaseAnalyze; 55 TestCaseAnalyzeBuiltin[] mutationTestCaseBuiltin; 56 57 long mutationTesterRuntime; 58 Nullable!long mutationId; 59 60 bool help; 61 bool shortPluginHelp; 62 bool dryRun; 63 64 MutationKind[] mutation; 65 MutationOrder mutationOrder; 66 67 ReportKind reportKind; 68 ReportLevel reportLevel; 69 ReportSection[] reportSection; 70 71 AdminOperation adminOp; 72 Mutation.Status mutantStatus; 73 Mutation.Status mutantToStatus; 74 string testCaseRegex; 75 76 ToolMode toolMode; 77 } 78 79 Data data; 80 alias data this; 81 82 private GetoptResult help_info; 83 84 alias GroupF = void delegate(string[]) @system; 85 GroupF[string] groups; 86 87 /** 88 * trusted: getopt is safe in dmd-2.077.0. 89 * Remove the trusted attribute when upgrading the minimal required version 90 * of the D frontend. 91 */ 92 void parse(string[] args) @trusted { 93 import std.traits : EnumMembers; 94 import std.format : format; 95 96 static import std.getopt; 97 98 const db_help = "sqlite3 database to use (default: dextool_mutate.sqlite3)"; 99 const restrict_help = "restrict analysis to files in this directory tree (default: .)"; 100 const out_help = "path used as the root for mutation/reporting of files (default: .)"; 101 102 void analyzerG(string[] args) { 103 data.toolMode = ToolMode.analyzer; 104 // dfmt off 105 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 106 "compile-db", "Retrieve compilation parameters from the file", &data.compileDb, 107 "db", db_help, &data.db, 108 "in", "Input file to parse (default: all files in the compilation database)", &data.inFiles, 109 "out", out_help, &data.outputDirectory, 110 "restrict", restrict_help, &data.restrictDir, 111 ); 112 // dfmt on 113 } 114 115 void generateMutantG(string[] args) { 116 data.toolMode = ToolMode.generate_mutant; 117 string cli_mutation_id; 118 // dfmt off 119 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 120 "db", db_help, &data.db, 121 "out", out_help, &data.outputDirectory, 122 "restrict", restrict_help, &data.restrictDir, 123 "id", "mutate the source code as mutant ID", &cli_mutation_id, 124 ); 125 // dfmt on 126 127 try { 128 import std.conv : to; 129 130 if (cli_mutation_id.length != 0) 131 data.mutationId = cli_mutation_id.to!long; 132 } 133 catch (ConvException e) { 134 logger.infof("Invalid mutation point '%s'. It must be in the range [0, %s]", 135 cli_mutation_id, long.max); 136 } 137 } 138 139 void testMutantsG(string[] args) { 140 data.toolMode = ToolMode.test_mutants; 141 // dfmt off 142 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 143 "compile", "program to use to compile the mutant", &data.mutationCompile, 144 "db", db_help, &data.db, 145 "dry-run", "do not write data to the filesystem", &data.dryRun, 146 "mutant", "kind of mutation to test " ~ format("[%(%s|%)]", [EnumMembers!MutationKind]), &data.mutation, 147 "order", "determine in what order mutations are chosen " ~ format("[%(%s|%)]", [EnumMembers!MutationOrder]), &data.mutationOrder, 148 "out", out_help, &data.outputDirectory, 149 "restrict", restrict_help, &data.restrictDir, 150 "test", "program used to run the test suite", &data.mutationTester, 151 "test-case-analyze-builtin", "builtin analyzer of output from testing frameworks to find failing test cases", &data.mutationTestCaseBuiltin, 152 "test-case-analyze-cmd", "program used to find what test cases killed the mutant", &data.mutationTestCaseAnalyze, 153 "test-timeout", "timeout to use for the test suite (msecs)", &data.mutationTesterRuntime, 154 ); 155 // dfmt on 156 } 157 158 void reportG(string[] args) { 159 data.toolMode = ToolMode.report; 160 // dfmt off 161 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 162 "db", db_help, &data.db, 163 "level", "the report level of the mutation data " ~ format("[%(%s|%)]", [EnumMembers!ReportLevel]), &data.reportLevel, 164 "out", out_help, &data.outputDirectory, 165 "restrict", restrict_help, &data.restrictDir, 166 "mutant", "kind of mutation to report " ~ format("[%(%s|%)]", [EnumMembers!MutationKind]), &data.mutation, 167 "section", "sections to include in the report " ~ format("[%(%s|%)]", [EnumMembers!ReportSection]), &data.reportSection, 168 "style", "kind of report to generate " ~ format("[%(%s|%)]", [EnumMembers!ReportKind]), &data.reportKind, 169 ); 170 // dfmt on 171 172 if (data.reportSection.length != 0 && data.reportLevel != ReportLevel.summary) { 173 logger.error("Combining --section and --level is not supported"); 174 help_info.helpWanted = true; 175 } 176 } 177 178 void adminG(string[] args) { 179 data.toolMode = ToolMode.admin; 180 // dfmt off 181 help_info = getopt(args, std.getopt.config.keepEndOfOptions, 182 "db", db_help, &data.db, 183 "mutant", "mutants to operate on " ~ format("[%(%s|%)]", [EnumMembers!MutationKind]), &data.mutation, 184 "operation", "administrative operation to perform " ~ format("[%(%s|%)]", [EnumMembers!AdminOperation]), &data.adminOp, 185 "test-case-regex", "regex to use when removing test cases", &data.testCaseRegex, 186 "status", "change the state of the mutants --to-status unknown which currently have status " ~ format("[%(%s|%)]", [EnumMembers!(Mutation.Status)]), &data.mutantStatus, 187 "to-status", "reset mutants to state (default: unknown) " ~ format("[%(%s|%)]", [EnumMembers!(Mutation.Status)]), &data.mutantToStatus, 188 ); 189 // dfmt on 190 } 191 192 groups["analyze"] = &analyzerG; 193 groups["generate"] = &generateMutantG; 194 groups["test"] = &testMutantsG; 195 groups["report"] = &reportG; 196 groups["admin"] = &adminG; 197 198 if (args.length == 2 && args[1] == "--short-plugin-help") { 199 shortPluginHelp = true; 200 return; 201 } 202 203 if (args.length < 2) { 204 logger.error("Missing command"); 205 help = true; 206 return; 207 } 208 209 const string cg = args[1]; 210 string[] subargs = args[0 .. 1]; 211 if (args.length > 2) 212 subargs ~= args[2 .. $]; 213 214 if (auto f = cg in groups) { 215 try { 216 (*f)(subargs); 217 help = help_info.helpWanted; 218 } 219 catch (std.getopt.GetOptException ex) { 220 logger.error(ex.msg); 221 help = true; 222 } 223 catch (Exception ex) { 224 logger.error(ex.msg); 225 help = true; 226 } 227 } else { 228 logger.error("Unknown command: ", cg); 229 help = true; 230 return; 231 } 232 233 if (restrictDir.length == 0) 234 restrictDir = ["."]; 235 236 import std.algorithm : find; 237 import std.array : array; 238 import std.range : drop; 239 240 cflags = args.find("--").drop(1).array(); 241 } 242 243 /** 244 * Trusted: 245 * The only input is a static string and data derived from getopt itselt. 246 * Assuming that getopt in phobos behave well. 247 */ 248 void printHelp() @trusted { 249 import std.array : array; 250 import std.algorithm : joiner, sort, map; 251 import std.ascii : newline; 252 import std.stdio : writeln; 253 254 string base_help = "Usage: dextool mutate COMMAND [options]"; 255 256 switch (toolMode) with (ToolMode) { 257 case none: 258 writeln("commands: ", newline, 259 groups.byKey.array.sort.map!(a => " " ~ a).joiner(newline)); 260 break; 261 case analyzer: 262 base_help = "Usage: dextool mutate analyze [options] [-- CFLAGS...]"; 263 break; 264 case generate_mutant: 265 break; 266 case test_mutants: 267 logger.errorf("--mutant possible values: %(%s|%)", [EnumMembers!MutationKind]); 268 logger.errorf("--order possible values: %(%s|%)", [EnumMembers!MutationOrder]); 269 logger.errorf("--test-case-analyze-builtin possible values: %(%s|%)", 270 [EnumMembers!TestCaseAnalyzeBuiltin]); 271 break; 272 case report: 273 logger.errorf("--mutant possible values: %(%s|%)", [EnumMembers!MutationKind]); 274 logger.errorf("--report possible values: %(%s|%)", [EnumMembers!ReportKind]); 275 logger.errorf("--level possible values: %(%s|%)", [EnumMembers!ReportLevel]); 276 logger.errorf("--section possible values: %(%s|%)", [EnumMembers!ReportSection]); 277 break; 278 case admin: 279 logger.errorf("--mutant possible values: %(%s|%)", [EnumMembers!MutationKind]); 280 logger.errorf("--operation possible values: %(%s|%)", [EnumMembers!AdminOperation]); 281 logger.errorf("--status possible values: %(%s|%)", [EnumMembers!(Mutation.Status)]); 282 break; 283 default: 284 break; 285 } 286 287 defaultGetoptPrinter(base_help, help_info.options); 288 } 289 }