1 module covered;
2 
3 version(unittest) import beep;
4 
5 import core.stdc.stdio;
6 import core.stdc.stdlib : free, exit;
7 
8 version(unittest) { } else extern(C) @nogc int main(int argc, char** argv) {
9 	import core.stdc..string : strncpy, strcmp;
10 
11 	if(argc == 1)
12 		argv[0].usage;
13 
14 	for(size_t i = 1; i < argc; ++i) {
15 		if(!argv[i].strcmp("-h")) {
16 			argv[0].usage;
17 		} else if(!argv[i].strcmp("-a")) {
18 			float total = 0.0;
19 			size_t count;
20 
21 			if(argc - i == 1)
22 				"At least 1 input file is expected".fail;
23 
24 			for(size_t j = i + 1; j < argc; ++j) {
25 				auto file = fopen(argv[j], "r");
26 				if(file is null)
27 					"Could not open %s".fail(argv[j]);
28 
29 				scope(exit) file.fclose;
30 
31 				auto r = file.getCoverage;
32 				if(r != float.infinity) {
33 					total += r;
34 					++count;
35 				}
36 			}
37 
38 			"Average: %.2f%%\n".printf(total / count);
39 
40 			break;
41 		} else if(!argv[i].strcmp("-b")) {
42 			if(argc - i < 3)
43 				"At least 2 input files are expected".fail;
44 
45 			for(size_t j = i + 1; j < argc; ++j) {
46 				auto file = fopen(argv[j], "r");
47 				if(file is null)
48 					"Could not open %s".fail(argv[j]);
49 
50 				scope(exit) file.fclose;
51 
52 				auto coverage = file.getCoverage;
53 				auto name = file.getSourceFile;
54 				scope(exit) free(cast(void*) name);
55 
56 				coverage == float.infinity 
57 					? "%40s has no code\n".printf(name)
58 					: "%40s %.2f%%\n".printf(name, coverage);
59 			}
60 			break;
61 		} else {
62 			"Unknown argument '%s'\n".printf(argv[i]);
63 			argv[0].usage;
64 		}
65 	}
66 
67 	return 0;
68 }
69 
70 extern(C) @nogc void usage(char* program_name) {
71 	printf("Usage: %s <options> FILE...\n", program_name);
72 
73 	exit(0);
74 }
75 
76 extern(C) @nogc void fail(A...)(scope const(char)* fmt, A args) {
77 	stderr.fprintf(fmt, args);
78 	stderr.fprintf("\n");
79 	exit(-1);
80 }
81 
82 extern(C) @nogc nothrow const(char)* getSourceFile(FILE* file) {
83 	import core.stdc.stdlib : malloc;
84 	import core.stdc..string : strrchr, strncpy;
85 
86 	char[1024] buffer;
87 
88 	file.fseek(0, SEEK_SET);
89 
90 	while(fgets(buffer.ptr, buffer.length, file) !is null) {}
91 
92 	auto end = buffer.ptr.strrchr('i'); // Line looks like `<filename> is <percent>% covered`
93 
94 	if(end is null) { // It's possible that file has no code
95 		end = buffer.ptr.strrchr('h');
96 
97 		if(end is null)
98 			"Could not find source file name. Invalid file?".fail;
99 	}
100 	
101 	auto r = cast(char*) malloc(end - buffer.ptr - 1);
102 
103 	strncpy(r, buffer.ptr, end - buffer.ptr - 1);
104 
105 	return r;
106 }
107 
108 @("getSourceFile()")
109 unittest {
110 	import std..string : fromStringz;
111 	{
112 		auto file = fopen("test/hello.lst", "r");
113 		if(file is null)
114 			throw new Exception("Could not open file");
115 
116 		scope(exit) file.fclose;
117 
118 		auto p = file.getSourceFile;
119 		scope(exit) free(cast(void*) p);
120 
121 		p.fromStringz.expect!equal("hello.d");
122 	}
123 
124 	{
125 		auto file = fopen("test/empty.lst", "r");
126 		if(file is null)
127 			throw new Exception("Could not open file");
128 
129 		scope(exit) file.fclose;
130 
131 		auto p = file.getSourceFile;
132 		scope(exit) free(cast(void*) p);
133 
134 		p.fromStringz.expect!equal("empty.d");
135 	}
136 }
137 
138 extern(C) @nogc nothrow float getCoverage(FILE* file) {
139 	import core.stdc.stdlib : atoi;
140 	import core.stdc..string : strchr, strlen;
141 
142 	char[4096] buffer;
143 
144 	size_t covered;
145 	size_t total;
146 
147 	file.fseek(0, SEEK_SET);
148 
149 	while(fgets(buffer.ptr, buffer.length, file) !is null) {
150 		auto len = buffer.ptr.strlen;
151 		if(len < 8 || buffer.ptr.strchr('|') is null)
152 			continue;
153 
154 		if(buffer[0] == '0') {
155 			++total;
156 		} else if(buffer.ptr.atoi != 0) {
157 			++total;
158 			++covered;
159 		}
160 	}
161 
162 	if(total == 0)
163 		return float.infinity;
164 
165 	return cast(float) covered / cast(float) total * 100.0f;
166 }
167 
168 @("getCoverage()")
169 unittest {
170 	auto file = fopen("test/hello.lst", "r");
171 	if(file is null)
172 		throw new Exception("Could not open file");
173 
174 	scope(exit) file.fclose;
175 
176 	file.getCoverage.expect!equal(100.0f);
177 }
178 
179 @("getCoverage() returns infinity on files that have no code")
180 unittest {
181 	auto file = fopen("test/empty.lst", "r");
182 	if(file is null)
183 		throw new Exception("Could not open file");
184 
185 	scope(exit) file.fclose;
186 
187 	file.getCoverage.expect!equal(float.infinity);
188 }