File Coverage

quickcover.xs
Criterion Covered Total %
statement 175 182 96.1
branch 102 220 46.3
condition n/a
subroutine n/a
pod n/a
total 277 402 68.9


line stmt bran cond sub pod time code
1             #include
2             #include
3             #include
4             #include
5             #include "glog.h"
6             #include "gmem.h"
7             #include "cover.h"
8             #include "util.h"
9              
10             #define QC_PREFIX "QC"
11             #define QC_EXTENSION ".txt"
12              
13             #define QC_PACKAGE "Devel::QuickCover"
14             #define QC_CONFIG_VAR QC_PACKAGE "::CONFIG"
15              
16             #define QC_CONFIG_OUTPUTDIR "output_directory"
17             #define QC_CONFIG_METADATA "metadata"
18             #define QC_CONFIG_NOATEXIT "noatexit"
19              
20             #ifndef OpSIBLING
21             #define OpSIBLING(op) ((op)->op_sibling)
22             #endif
23              
24             static Perl_ppaddr_t nextstate_orig = 0, dbstate_orig = 0;
25             static peep_t peepp_orig;
26             static CoverList* cover = 0;
27             static int enabled = 0;
28             static Buffer output_dir;
29             static Buffer metadata;
30              
31             static void qc_init(int noatexit);
32             static void qc_fini(void);
33              
34             static void qc_terminate(int nodump);
35             static void qc_install(pTHX);
36             static OP* qc_nextstate(pTHX);
37             static void qc_peep(pTHX_ OP* o);
38             static void qc_dump(CoverList* cover);
39              
40             static void save_stuff(pTHX);
41             static void save_output_directory(pTHX);
42             static void save_metadata(pTHX);
43              
44             static void scan_optree(pTHX_ CoverList* cover, OP* op);
45              
46 7           static void qc_init(int noatexit)
47             {
48 7 50         if (!noatexit) {
49             GLOG(("Registering atexit handler"));
50 7           atexit(qc_fini);
51             }
52              
53 7           gmem_init();
54 7 50         buffer_init(&output_dir, 0);
55 7 50         buffer_init(&metadata, 0);
56              
57 7 50         buffer_append(&output_dir, "/tmp", 0);
    0          
    0          
    0          
58 7 50         buffer_terminate(&output_dir);
59              
60 7 50         buffer_terminate(&metadata);
61 7           }
62              
63 14           static void qc_terminate(int nodump)
64             {
65 14 100         if (cover) {
66 7 50         if (nodump) {
67             GLOG(("Skipping dumping cover data"));
68             } else {
69 7           qc_dump(cover);
70             }
71 7           cover_destroy(cover);
72 7           cover = 0;
73             }
74              
75 14 100         buffer_fini(&metadata);
    50          
76 14 100         buffer_fini(&output_dir);
    50          
77 14           gmem_fini();
78 14           }
79              
80 7           static void qc_fini(void)
81             {
82 7           qc_terminate(0);
83 7           }
84              
85 7           static void qc_install(pTHX)
86             {
87 7 50         if (PL_ppaddr[OP_NEXTSTATE] == qc_nextstate) {
88 0           die("%s: internal error, exiting: qc_install called again", QC_PACKAGE);
89             }
90              
91 7           nextstate_orig = PL_ppaddr[OP_NEXTSTATE];
92 7           PL_ppaddr[OP_NEXTSTATE] = qc_nextstate;
93 7           dbstate_orig = PL_ppaddr[OP_DBSTATE];
94 7           PL_ppaddr[OP_DBSTATE] = qc_nextstate;
95 7           peepp_orig = PL_peepp;
96 7           PL_peepp = qc_peep;
97              
98             GLOG(("qc_install: nextstate_orig is [%p]", nextstate_orig));
99             GLOG(("qc_install: qc_nextstate is [%p]", qc_nextstate));
100 7           }
101              
102             #if PERL_VERSION >= 18 && PERL_VERSION < 22
103              
104             static void named_cv_name(pTHX_ SV* dest, CV* cv) {
105             HV* stash = CvSTASH(cv);
106             const char* name = stash ? HvNAME(stash) : NULL;
107              
108             if (name) {
109             /* inspired by Perl_gv_fullname4 */
110             const STRLEN len = HvNAMELEN(stash);
111              
112             sv_setpvn(dest, name, len);
113             if (HvNAMEUTF8(stash))
114             SvUTF8_on(dest);
115             else
116             SvUTF8_off(dest);
117             sv_catpvs(dest, "::");
118             sv_catsv(dest, sv_2mortal(newSVhek(CvNAME_HEK(cv))));
119             }
120             }
121              
122             #endif
123              
124 39           static void add_sub_helper(pTHX_ CoverList* cover, const char* file, const char* name, U32 line) {
125             U32 file_hash, name_hash;
126              
127 39           PERL_HASH(file_hash, file, strlen(file));
128 39           PERL_HASH(name_hash, name, strlen(name));
129 39           cover_sub_add_sub(cover, file, file_hash, name, name_hash, line);
130 39           }
131              
132 26           static void add_covered_sub_helper(pTHX_ CoverList* cover, const char* file, const char* name, U32 line, int phase) {
133             U32 file_hash, name_hash;
134              
135 26           PERL_HASH(file_hash, file, strlen(file));
136 26           PERL_HASH(name_hash, name, strlen(name));
137 26           cover_sub_add_covered_sub(cover, file, file_hash, name, name_hash, line, phase);
138 26           }
139              
140 171           static void add_line_helper(pTHX_ CoverList* cover, const char* file, U32 line) {
141             U32 file_hash;
142              
143 171           PERL_HASH(file_hash, file, strlen(file));
144 171           cover_add_line(cover, file, file_hash, line);
145 171           }
146              
147 131           static void add_covered_line_helper(pTHX_ CoverList* cover, const char* file, U32 line, int phase) {
148             U32 file_hash;
149              
150 131           PERL_HASH(file_hash, file, strlen(file));
151 131           cover_add_covered_line(cover, file, file_hash, line, phase);
152 131           }
153              
154 26           static OP* qc_first_nextstate(pTHX) {
155 26           const PERL_CONTEXT* cx = &cxstack[cxstack_ix];
156              
157             /* this should always be true, but just in case */
158 26 50         if (CxTYPE(cx) == CXt_SUB) {
159 26           CV* cv = cx->blk_sub.cv;
160 26           SV* dest = sv_newmortal();
161 26           GV* gv = CvGV(cv);
162              
163             /* Create data structure if necessary. */
164 26 50         if (!cover) {
165 0           cover = cover_create();
166             GLOG(("qc_first_nextstate: created cover data [%p]", cover));
167             }
168              
169 26 50         if (gv) { /* see the same condition in qc_peep */
170 26           gv_efullname3(dest, gv, NULL);
171 26 50         add_covered_sub_helper(aTHX_ cover, GvFILE(gv), SvPV_nolen(dest), CopLINE(cCOPx(PL_op)), PL_phase);
    50          
172             #if PERL_VERSION >= 18 && PERL_VERSION < 22
173             } else if (CvNAMED(cv)) {
174             named_cv_name(aTHX_ dest, cv);
175             add_covered_sub_helper(aTHX_ cover, CvFILE(cv), SvPV_nolen(dest), CopLINE(cCOPx(PL_op)), PL_phase);
176             #endif
177             }
178             }
179              
180 26           return qc_nextstate(aTHX);
181             }
182              
183 211           static OP* qc_nextstate(pTHX) {
184 211 50         Perl_ppaddr_t orig_pp = PL_op->op_type == OP_NEXTSTATE ? nextstate_orig : dbstate_orig;
185 211           OP* ret = orig_pp(aTHX);
186              
187 211 100         if (enabled) {
188             /* Restore original nextstate op for this node. */
189 131 100         if (PL_op->op_ppaddr == qc_nextstate)
190 105           PL_op->op_ppaddr = orig_pp;
191              
192             /* Create data structure if necessary. */
193 131 100         if (!cover) {
194 1           cover = cover_create();
195             GLOG(("qc_nextstate: created cover data [%p]", cover));
196             }
197              
198             /* Now do our own nefarious tracking... */
199 131 50         add_covered_line_helper(aTHX_ cover, CopFILE(PL_curcop), CopLINE(PL_curcop), PL_phase);
200             }
201              
202 211           return ret;
203             }
204              
205 85           static void qc_peep(pTHX_ OP *o)
206             {
207 85 50         if (!o || o->op_opt)
    50          
208 0           return;
209              
210 85           peepp_orig(aTHX_ o);
211              
212 85 100         if (enabled) {
213             /* Create data structure if necessary. */
214 51 100         if (!cover) {
215 6           cover = cover_create();
216             GLOG(("qc_peep: created cover data [%p]", cover));
217             }
218              
219             /*
220             * the peephole is called on the start op, and should proceed
221             * in execution order, but we cheat because we don't need
222             * execution order and it's much simpler to perform a
223             * recursive scan of the tree
224             *
225             * This would be much simpler, and more natural, as a
226             * PL_check[OP_NEXTSTATE] override, but guess what? Perl does
227             * not call the check hook for OP_NEXTSTATE/DBSTATE
228             */
229 90 50         if (PL_compcv && o == CvSTART(PL_compcv) && CvROOT(PL_compcv)) {
    100          
    50          
230             /* the first nextstate op marks the sub as covered */
231             OP* f;
232 41 50         for (f = o; f; f = f->op_next) {
233 41 100         if (f->op_type == OP_NEXTSTATE || f->op_type == OP_DBSTATE) {
    50          
234 39           f->op_ppaddr = qc_first_nextstate;
235 39           break;
236             }
237             }
238 39 50         if (f) {
239 39           GV* gv = CvGV(PL_compcv);
240 39           SV* dest = sv_newmortal();
241              
242 39 50         if (gv) { /* for example lexical subs don't have a GV on Perl < 5.22 */
243 39           gv_efullname3(dest, gv, NULL);
244 39 50         add_sub_helper(aTHX_ cover, GvFILE(gv), SvPV_nolen(dest), CopLINE(cCOPx(f)));
    50          
245             #if PERL_VERSION >= 18 && PERL_VERSION < 22
246             } else if (CvNAMED(PL_compcv)) {
247             named_cv_name(aTHX_ dest, PL_compcv);
248             add_sub_helper(aTHX_ cover, CvFILE(PL_compcv), SvPV_nolen(dest), CopLINE(cCOPx(f)));
249             #endif
250             }
251             }
252 39           scan_optree(aTHX_ cover, CvROOT(PL_compcv));
253 12 100         } else if (o == PL_main_start && PL_main_root)
    50          
254 6           scan_optree(aTHX_ cover, PL_main_root);
255 6 50         else if (o == PL_eval_start && PL_eval_root)
    50          
256 6           scan_optree(aTHX_ cover, PL_eval_root);
257             }
258             }
259              
260 7           static void qc_dump(CoverList* cover)
261             {
262             static int count = 0;
263             static time_t last = 0;
264              
265 7           time_t t = 0;
266 7           FILE* fp = 0;
267             char base[1024];
268             char tmp[1024];
269             char txt[1024];
270             struct tm now;
271              
272             /*
273             * If current time is different from last time (seconds
274             * resolution), reset file suffix counter to zero.
275             */
276 7           t = time(0);
277 7 50         if (last != t) {
278 7           last = t;
279 7           count = 0;
280             }
281              
282             /*
283             * Get detailed current time:
284             */
285 7           localtime_r(&t, &now);
286              
287             /*
288             * We generate the information on a file with the following structure:
289             *
290             * output_dir/prefix_YYYYMMDD_hhmmss_pid_NNNNN.txt
291             *
292             * where NNNNN is a suffix counter to allow for more than one file in a
293             * single second interval.
294             */
295 28           sprintf(base, "%s_%04d%02d%02d_%02d%02d%02d_%ld_%05d",
296             QC_PREFIX,
297 14           now.tm_year + 1900, now.tm_mon + 1, now.tm_mday,
298             now.tm_hour, now.tm_min, now.tm_sec,
299 7           (long) getpid(),
300             count++);
301              
302             /*
303             * We generate the information on a file with a prepended dot. Once we are
304             * done, we atomically rename it and get rid of the dot. This way, any job
305             * polling for new files will not find any half-done work.
306             */
307 7           sprintf(tmp, "%s/.%s%s", output_dir.data, base, QC_EXTENSION);
308 7           sprintf(txt, "%s/%s%s" , output_dir.data, base, QC_EXTENSION);
309              
310             GLOG(("qc_dump: dumping cover data [%p] to file [%s]", cover, txt));
311 7           fp = fopen(tmp, "w");
312 7 50         if (!fp) {
313             GLOG(("qc_dump: could not create dump file [%s]", tmp));
314             } else {
315 7           fprintf(fp, "{");
316              
317 7           fprintf(fp, "\"date\":\"%04d-%02d-%02d\",",
318 14           now.tm_year + 1900, now.tm_mon + 1, now.tm_mday);
319 7           fprintf(fp, "\"time\":\"%02d:%02d:%02d\",",
320             now.tm_hour, now.tm_min, now.tm_sec);
321              
322 7           fprintf(fp, "\"metadata\":%s,", metadata.data);
323 7           cover_dump(cover, fp);
324              
325 7           fprintf(fp, "}\n");
326 7           fclose(fp);
327 7           rename(tmp, txt);
328             }
329 7           }
330              
331 14           static void save_stuff(pTHX)
332             {
333 14           save_output_directory(aTHX);
334 14           save_metadata(aTHX);
335 14           }
336              
337 14           static void save_output_directory(pTHX)
338             {
339 14           HV* qc_config = 0;
340 14           SV** val = 0;
341 14           STRLEN len = 0;
342             const char* str;
343              
344 14           qc_config = get_hv(QC_CONFIG_VAR, 0);
345 14 50         if (!qc_config) {
346 0           die("%s: Internal error, exiting: %s must exist",
347             QC_PACKAGE, QC_CONFIG_VAR);
348             }
349 14           val = hv_fetch(qc_config, QC_CONFIG_OUTPUTDIR,
350             sizeof(QC_CONFIG_OUTPUTDIR) - 1, 0);
351 14 100         if (!SvUTF8(*val)) {
352 7           sv_utf8_upgrade(*val);
353             }
354 14 50         str = SvPV_const(*val, len);
355              
356 14 50         buffer_reset(&output_dir);
357 14 50         buffer_append(&output_dir, str, len);
    50          
    0          
    0          
    0          
358 14 50         buffer_terminate(&output_dir);
359 14           }
360              
361 14           static void save_metadata(pTHX)
362             {
363 14           HV* qc_config = 0;
364 14           SV** val = 0;
365             HV* hv;
366              
367 14           qc_config = get_hv(QC_CONFIG_VAR, 0);
368 14 50         if (!qc_config) {
369 0           die("%s: Internal error, exiting: %s must exist",
370             QC_PACKAGE, QC_CONFIG_VAR);
371             }
372 14           val = hv_fetch(qc_config, QC_CONFIG_METADATA,
373             sizeof(QC_CONFIG_METADATA) - 1, 0);
374 14 50         if (!SvROK(*val) || SvTYPE(SvRV(*val)) != SVt_PVHV) {
    50          
375 0           die("%s: Internal error, exiting: %s must be a hashref",
376             QC_PACKAGE, QC_CONFIG_METADATA);
377             }
378              
379 14           hv = (HV*) SvRV(*val);
380 14 50         buffer_reset(&metadata);
381 14           dump_hash(aTHX_ hv, &metadata);
382 14 50         buffer_terminate(&metadata);
383             GLOG(("Saved metadata [%s]", metadata.data));
384 14           }
385              
386 1723           static void scan_optree(pTHX_ CoverList* cover, OP* op)
387             {
388 1723 100         if (op->op_flags & OPf_KIDS) {
389             OP* curr;
390              
391 2315 100         for (curr = cUNOPx(op)->op_first; curr; curr = OpSIBLING(curr))
    100          
392 1672           scan_optree(aTHX_ cover, curr);
393             }
394              
395 1723 100         if (op->op_type == OP_NEXTSTATE || op->op_type == OP_DBSTATE)
    50          
396 171 50         add_line_helper(aTHX_ cover, CopFILE(cCOPx(op)), CopLINE(cCOPx(op)));
397 1723           }
398              
399              
400             MODULE = Devel::QuickCover PACKAGE = Devel::QuickCover
401             PROTOTYPES: DISABLE
402              
403             #################################################################
404              
405             BOOT:
406             GLOG(("@@@ BOOT"));
407 7           qc_install(aTHX);
408              
409             void
410             start()
411             PREINIT:
412 7           HV* qc_config = 0;
413 7           SV** val = 0;
414 7           int noatexit = 0;
415             CODE:
416 7 50         if (enabled) {
417             GLOG(("@@@ start(): ignoring multiple calls"));
418             } else {
419             GLOG(("@@@ start(): enabling Devel::QuickCover"));
420              
421 7           qc_config = get_hv(QC_CONFIG_VAR, 0);
422 7 50         if (!qc_config) {
423 0           die("%s: Internal error, exiting: %s must exist",
424             QC_PACKAGE, QC_CONFIG_VAR);
425             }
426 7           val = hv_fetch(qc_config, QC_CONFIG_NOATEXIT,
427             sizeof(QC_CONFIG_NOATEXIT) - 1, 0);
428 7 50         noatexit = val && SvTRUE(*val);
    50          
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
    0          
429              
430 7           enabled = 1;
431 7           qc_init(noatexit);
432 7           save_stuff(aTHX);
433             }
434              
435             void
436             end(...)
437             PREINIT:
438 8           int nodump = 0;
439             CODE:
440 8 100         if (!enabled) {
441             GLOG(("@@@ end(): ignoring multiple calls"));
442             } else {
443 7 100         if (items >= 1) {
444 6           SV* pnodump = ST(0);
445 6 50         nodump = SvTRUE(pnodump);
    50          
    0          
    50          
    0          
    0          
    50          
    0          
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
    0          
    50          
    0          
446             }
447             GLOG(("@@@ end(%d): dumping data and disabling Devel::QuickCover", nodump));
448 7           save_stuff(aTHX);
449 7           qc_terminate(nodump);
450 7           enabled = 0;
451             }