1 /** 2 Copyright: Copyright (c) 2021, 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 app; 11 12 import logger = std.experimental.logger; 13 import std.algorithm; 14 import std.array; 15 import std.conv : to; 16 import std.format : format; 17 import std.path : relativePath; 18 import std.stdio : File; 19 20 import colorlog; 21 import my.path; 22 23 int main(string[] args) { 24 import std.format : format; 25 static import std.getopt; 26 import std.file : thisExePath; 27 import std.path : baseName; 28 29 confLogger(VerboseMode.info); 30 31 bool var; 32 AbsolutePath[] searchDirs; 33 auto relative = AbsolutePath("."); 34 auto output = AbsolutePath("test_metadata.json"); 35 try { 36 string[] searchDirsRaw; 37 string relativeRaw; 38 string outputRaw; 39 auto helpInfo = std.getopt.getopt(args, std.getopt.config.required, "d|directory", 40 "directory to search for googletest test cases", &searchDirsRaw, 41 "r|relative", "paths are relative this path", 42 &relativeRaw, "o|output", "write the json output to this file", &outputRaw); 43 searchDirs = searchDirsRaw.map!(a => AbsolutePath(a)).array; 44 if (!relativeRaw.empty) 45 relative = AbsolutePath(relativeRaw); 46 if (!outputRaw.empty) 47 output = AbsolutePath(outputRaw); 48 49 if (helpInfo.helpWanted) { 50 std.getopt.defaultGetoptPrinter(format!"usage: %s <options>\n"(thisExePath.baseName), 51 helpInfo.options); 52 } 53 } catch (Exception e) { 54 logger.error(e.msg); 55 return 1; 56 } 57 58 auto testCases = appender!(TestCase[])(); 59 60 foreach (d; searchDirs) { 61 logger.info("Searching ", d); 62 try { 63 import std.file : dirEntries, SpanMode; 64 65 foreach (f; dirEntries(d.toString, SpanMode.depth).filter!"a.isFile" 66 .map!(a => AbsolutePath(a.name))) { 67 logger.info("Parsing ", f); 68 auto found = parse(f); 69 foreach (tc; found) 70 logger.info(" ", tc.name, " at ", tc.line); 71 testCases.put(found); 72 } 73 } catch (Exception e) { 74 } 75 } 76 77 import std.json; 78 79 auto jrval = appender!(JSONValue[])(); 80 81 foreach (testCase; testCases.data) { 82 JSONValue tc; 83 tc["name"] = testCase.name; 84 JSONValue loc; 85 loc["file"] = relativePath(testCase.file.toString, relative.toString); 86 loc["line"] = testCase.line; 87 tc["location"] = loc; 88 89 jrval.put(tc); 90 } 91 92 logger.info("Saving result to ", output); 93 File(output.toString, "w").write(JSONValue(jrval.data).toPrettyString); 94 95 return 0; 96 } 97 98 struct TestCase { 99 AbsolutePath file; 100 uint line; 101 string name; 102 } 103 104 TestCase[] parse(AbsolutePath testPath) { 105 import std.ascii : isWhite; 106 import std..string : startsWith, strip; 107 108 static TestCase parseTest(string[] lines) { 109 TestCase tc; 110 string def; 111 bool open; 112 int parenthesis; 113 114 foreach (c; lines.joiner) { 115 if (c == '(') { 116 open = true; 117 parenthesis++; 118 } else if (c == ')') 119 parenthesis--; 120 else if (parenthesis == 1 && !c.isWhite) 121 def ~= c; 122 123 if (open && parenthesis == 0) 124 break; 125 } 126 127 auto s = split(def, ','); 128 if (s.length == 2 && !s[0].empty && !s[1].empty) { 129 tc.name = format!"%s.%s"(s[0].strip, s[1].strip); 130 } 131 132 return tc; 133 } 134 135 static TestCase parseTestParam(string[] lines) { 136 TestCase tc; 137 string def; 138 bool open; 139 int commas; 140 141 foreach (c; lines.joiner) { 142 if (c == '(') 143 open = true; 144 else if (c == ',') 145 commas++; 146 else if (open && !c.isWhite) 147 def ~= c; 148 149 if (open && commas == 2) 150 break; 151 } 152 153 if (def.empty) 154 return tc; 155 156 auto s = split(def[0 .. $ - 1], ','); 157 if (s.length == 2 && !s[0].empty && !s[1].empty) 158 tc.name = format!"%s.%s"(s[0].strip, s[1].strip); 159 160 return tc; 161 } 162 163 alias Parser = TestCase function(string[]); 164 Parser[string] parsers; 165 parsers["TEST"] = &parseTest; 166 parsers["TEST_F"] = &parseTest; 167 parsers["TEST_P"] = &parseTest; 168 parsers["INSTANTIATE_TEST_SUITE_P"] = &parseTestParam; 169 170 auto lines = File(testPath.toString).byLineCopy.array; 171 172 auto testCases = appender!(TestCase[])(); 173 174 foreach (i; 0 .. lines.length) { 175 () { 176 foreach (p; parsers.byKeyValue) { 177 try { 178 if (lines[i].startsWith(p.key)) { 179 auto tc = p.value()(lines[i .. $]); 180 tc.line = cast(uint) i + 1; 181 tc.file = testPath; 182 if (!tc.name.empty) 183 testCases.put(tc); 184 return; 185 } 186 } catch (Exception e) { 187 logger.info(e.msg); 188 } 189 } 190 }(); 191 } 192 193 return testCases.data; 194 }