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 }