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