1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost Software License 1.0)
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 */
6 module dextool_test.integration;
7 
8 import scriptlike;
9 import unit_threaded : shouldEqual;
10 
11 import dextool_test;
12 
13 enum globalTestdir = "fuzzer_tests";
14 
15 struct Compile {
16     enum Kind {
17         main,
18         fuzzTest,
19         original,
20     }
21 
22     Kind kind;
23     Path payload;
24     alias payload this;
25 }
26 
27 struct TestParams {
28     bool doCompare = true;
29     bool doCompile = true;
30 
31     Path root;
32     Path testRoot;
33 
34     Path input;
35     Path input_impl;
36     Path[2][] output;
37 
38     Compile[] compileFiles;
39 
40     // dextool parameters;
41     string[] dexParams;
42     string[] dexFlags;
43 
44     // Compiler
45     string[] compileFlags;
46     string[] compileIncls;
47     string originalCompilerVar;
48 }
49 
50 TestParams genTestParams(string hdr, string impl, const ref TestEnv testEnv) {
51     TestParams p;
52 
53     p.root = Path("plugin_testdata").absolutePath;
54     p.input = p.root ~ Path(hdr);
55     p.input_impl = p.root ~ Path(impl);
56     p.testRoot = p.input.dirName;
57 
58     p.output ~= [p.input ~ Ext(".hpp.ref"), testEnv.outdir ~ "dextool_default_scheduler.hpp"];
59 
60     p.compileFiles ~= Compile(Compile.Kind.main, testEnv.outdir ~ "main.cpp");
61     p.compileFiles ~= Compile(Compile.Kind.original, p.input_impl);
62 
63     p.dexParams = ["fuzzer", "--debug"];
64     p.dexFlags = [];
65 
66     p.compileFlags = compilerFlags();
67     p.compileIncls = ["-I" ~ p.input.dirName.toString];
68 
69     if (p.input_impl.extension == ".c") {
70         p.originalCompilerVar = "CC";
71     } else {
72         p.originalCompilerVar = "CXX";
73     }
74 
75     return p;
76 }
77 
78 void runTestFile(ref TestParams p, ref TestEnv testEnv, Flag!"sortLines" sortLines = No.sortLines,
79         Flag!"skipComments" skipComments = Yes.skipComments) {
80     dextoolYap("Input:%s", p.input.raw);
81     runDextool(p.input, testEnv, p.dexParams, p.dexFlags);
82 
83     if (p.doCompare) {
84         dextoolYap("Comparing");
85         auto input = p.input.stripExtension;
86         foreach (o; p.output) {
87             compareResult(sortLines, skipComments, GR(o[0], o[1]));
88         }
89     }
90 
91     if (p.doCompile) {
92         dextoolYap("Compiling");
93 
94         // these only exist AFTER dextool has executed.
95         auto comp_files = p.compileFiles ~ dirEntries(testEnv.outdir, "dextool_fuzz_case*.cpp",
96                 SpanMode.shallow).map!(a => Compile(Compile.Kind.fuzzTest, Path(a))).array();
97         dextoolYap("Found generated fuzz tests: %s", comp_files);
98 
99         compile(testEnv.outdir ~ "binary", comp_files, p.compileFlags,
100                 p.compileIncls, p.originalCompilerVar, testEnv);
101     }
102 }
103 
104 void compile(const Path dst_binary, Compile[] files, const string[] flags,
105         const string[] incls, string original_var, const ref TestEnv testEnv) {
106     import core.sys.posix.sys.stat;
107     import std.file : setAttributes;
108 
109     immutable bool[string] rm_flag = ["-Wpedantic" : true, "-Werror" : true, "-pedantic" : true];
110 
111     auto flags_ = flags.filter!(a => a !in rm_flag).array();
112 
113     Args compile_args;
114     compile_args ~= "-g";
115     compile_args ~= "-I" ~ testEnv.outdir.escapePath;
116     compile_args ~= "-I" ~ "support";
117     compile_args ~= incls.dup;
118 
119     Args wrapper_args;
120     wrapper_args ~= flags_.dup;
121     wrapper_args ~= "-o$OUT";
122     wrapper_args ~= files.filter!(a => a.kind != Compile.Kind.original)
123         .map!(a => a.payload).array().dup;
124     wrapper_args ~= (testEnv.outdir ~ "original.o").toString;
125     wrapper_args ~= "-l" ~ "dextoolfuzz";
126     wrapper_args ~= "-L.";
127 
128     Args original_args;
129     original_args ~= "-c";
130     original_args ~= "-o" ~ (testEnv.outdir ~ "original.o").toString;
131     original_args ~= files.filter!(a => a.kind == Compile.Kind.original)
132         .map!(a => a.payload).array().dup;
133 
134     string script_p = (testEnv.outdir ~ "build.sh").toString;
135     auto script = File(script_p, "w");
136     script.writeln("#!/bin/bash");
137     script.writeln("if [[ -z $CC ]]; then CC=gcc; fi");
138     script.writeln("if [[ -z $CXX ]]; then CXX=g++; fi");
139     script.writeln("if [[ -z $OUT ]]; then OUT=" ~ dst_binary.escapePath ~ "; fi");
140     script.writefln("$%s $FLAGS %s %s", original_var, compile_args.data, original_args.data);
141     script.writefln("$CXX $WRAPFLAGS %s %s", compile_args.data, wrapper_args.data);
142     script.close();
143 
144     auto script_attr = getAttributes(script_p);
145     script_attr = script_attr | S_IRWXU;
146     setAttributes(script_p, script_attr);
147 
148     runAndLog(script_p).status.shouldEqual(0);
149 }
150 
151 @(testId ~ "shall be a fuzzer environment for a void function")
152 unittest {
153     mixin(envSetup(globalTestdir));
154 
155     auto p = genTestParams("stage_1/a_void_func.h", "stage_1/a_void_func.c", testEnv);
156     p.doCompare = false;
157     runTestFile(p, testEnv);
158 }
159 
160 @(testId ~ "shall be a fuzzer environment for a function with parameters of primitive data types")
161 unittest {
162     mixin(envSetup(globalTestdir));
163 
164     auto p = genTestParams("stage_1/func_with_primitive_params.h",
165             "stage_1/func_with_primitive_params.c", testEnv);
166     p.doCompare = false;
167     runTestFile(p, testEnv);
168 }
169 
170 @(testId ~ "shall be a fuzzer environment for a function with int params and lots of if-stmt")
171 unittest {
172     mixin(envSetup(globalTestdir));
173 
174     auto p = genTestParams("stage_2/int_param_nested_if.hpp",
175             "stage_2/int_param_nested_if.cpp", testEnv);
176     p.doCompare = false;
177     runTestFile(p, testEnv);
178 }
179 
180 @(testId ~ "shall be a fuzzer environment with limits from a config file")
181 unittest {
182     mixin(envSetup(globalTestdir));
183 
184     auto p = genTestParams("stage_2/limit_params.hpp", "stage_2/limit_params.cpp", testEnv);
185     p.dexParams ~= "--config=" ~ (p.root ~ "stage_2/limit_params.xml").toString;
186     p.doCompare = false;
187     runTestFile(p, testEnv);
188 }
189 
190 @(testId ~ "shall be a fuzzer environment with complex structs and transforms from a config file ")
191 unittest {
192     mixin(envSetup(globalTestdir));
193 
194     auto p = genTestParams("stage_2/transform_param.hpp", "stage_2/transform_param.cpp", testEnv);
195     p.dexParams ~= "--config=" ~ (p.root ~ "stage_2/transform_param.xml").toString;
196     runTestFile(p, testEnv);
197 }
198 
199 @(testId ~ "shall be a fuzzer environment fuzzing in a limited range following a config")
200 unittest {
201     mixin(envSetup(globalTestdir));
202 
203     auto p = genTestParams("stage_2/user_param_fuzzer.hpp",
204             "stage_2/user_param_fuzzer.cpp", testEnv);
205     p.dexParams ~= "--config=" ~ (p.root ~ "stage_2/user_param_fuzzer.xml").toString;
206     p.doCompare = false;
207     runTestFile(p, testEnv);
208 }
209 
210 @("shall be a working, complete test of the helper library")
211 unittest {
212     mixin(envSetup(globalTestdir));
213 
214     auto p = genTestParams("", "test_lib/dummy.cpp", testEnv);
215     // dfmt off
216     auto comp_files =
217         [Compile(Compile.Kind.main, p.root ~ Path("test_lib") ~ "main.cpp"),
218         Compile(Compile.Kind.original, p.root ~ Path("test_lib") ~ "dummy.cpp")]
219             ~
220         ["test_fuzz_helper.cpp"]
221         .map!(a => p.root ~ Path("test_lib") ~ a)
222         .map!(a => Compile(Compile.Kind.fuzzTest, a))
223         .array();
224     // dfmt on
225 
226     compile(testEnv.outdir ~ "binary", comp_files, p.compileFlags,
227             p.compileIncls, p.originalCompilerVar, testEnv);
228 
229     Args test;
230     test ~= (testEnv.outdir ~ "binary").toString;
231     test ~= (p.root ~ Path("test_lib") ~ "raw_dummy_data.cpp").toString;
232 
233     runAndLog(test).status.shouldEqual(0);
234 }