forked from Gnucash/gnucash
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmake-testfile
executable file
·313 lines (278 loc) · 10.3 KB
/
make-testfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#!/usr/bin/perl -w
# -*- perl -*-
use strict;
use File::Spec;
sub process_func;
sub print_preamble;
sub scan_file;
sub process_function;
#Main
my ($author, $inpath, $outpath) = @ARGV;
die "Must provide author name and email" unless $author;
die "No file to process" unless $inpath;
my ($invol, $indir, $infile) = File::Spec->splitpath($inpath);
my ($outvol, $outdir, $outfile);
if ($outpath) {
($outvol, $outdir, $outfile) = File::Spec->splitpath($outpath);
} else {
$outvol = $invol;
$outdir = File::Spec->catdir($indir, "test");
$outfile = "utest-" . $infile;
$outpath = File::Spec->catpath($outvol, $outdir, $outfile);
}
open OUTFH, ">$outpath" or die "Failed to open $outpath: $!";
my $inname = print_preamble($infile, $author);
my $testlist = scan_file($inpath);
print OUTFH "\n\nvoid\ntest_suite_$inname (void)\n{\n\n";
print OUTFH join("\n", @$testlist), "\n";
print OUTFH "\n}\n";
close OUTFH;
#end
sub strip_comments {
my ($line, $comment) = @_;
my $comment_begin_re = qr{/\*.*};
my $comment_end_re = qr{.*\*/};
my $inline_comment_re = qr{/\*.*\*/};
my $rest_is_comment_re = qr{//.*$};
$line =~ s/$inline_comment_re//g;
$line =~ s/$rest_is_comment_re//;
if ($line =~ s/$comment_end_re//) {
return ($line, 0);
}
if ($line =~ s/$comment_begin_re//) {
return ($line, 1);
}
$line = "" if $comment;
return ($line, $comment);
}
sub scan_file {
my $inpath = shift;
my $func = [], $testlist = [];
my ($static, $body, $comment, $func_name) = (0, 0, 0, "");
open INFH, "<$inpath" or die "Failed to open $inpath: $!";
my $decl_or_def_re = qr{^(?:\w+[\w\d\s]+\s+)?(\w[\w\d]+)\s*\(};
my $preproc_re = qr/(?>^[\#}]|;$)/;
my @testlist;
while (my $line = <INFH>) {
chomp $line;
($line, $comment) = strip_comments($line);
next unless $line;
$body = 0 if $line =~ /^}/;
next if $body;
next if $line =~ /$preproc_re/;
$static = 1 if $line =~ /^static/;
if ( $line =~ /$decl_or_def_re/ ) {
$func_name = $1;
$body = 1 if $line =~ /{\s*$/;
}
push @$func, $line unless $line =~ /^[{}\/\s]/;
if ($body || $line =~ /^{/) {
push @$testlist, process_function($func_name, $static,
$func, $inpath) if $func_name;
$body = 1;
($static, $func_name) = (0, "");
$func = [];
}
}
close INFH;
return $testlist;
}
sub search_external {
my ($name, $inpath) = @_;
my ($invol, $indir, $infile) = File::Spec->splitpath($inpath);
my @excludes = qw(test* utest* swig* gnucash_core.c);
push @excludes, $infile;
my $exclude_string = "--exclude=" . join(" --exclude=", @excludes);
my $calls = `egrep -r $name --include="*.c" $exclude_string src | wc -l`;
chomp $calls;
$calls =~ s/\s//g;
my $files = `egrep -rl $name --include="*.c" $exclude_string src | wc -l`;
chomp $files;
$files =~ s/\s//g;
return ($calls, $files);
}
sub search_scheme {
my ($name) = @_;
$name =~ tr/_/-/;
my $egrepre = '\b' . $name . '[^\w_-]';
my $calls = `egrep -r "$egrepre" --include="*.scm" src | wc -l`;
chomp $calls;
$calls =~ s/\s//g;
my $files = `egrep -rl "$egrepre" --include="*.scm" src | wc -l`;
chomp $files;
$files =~ s/\s//g;
return ($calls, $files);
}
sub search_glade {
my ($name) = @_;
my $calls = `egrep -r $name --include="*.glade" --include=*.xml" --include=*.ui" src | wc -l`;
chomp $calls;
$calls =~ s/\s//g;
my $files = `egrep -rl $name --include="*.glade" --include=*.xml" --include=*.ui" src | wc -l`;
chomp $files;
$files =~ s/\s//g;
return ($calls, $files);
}
sub search_local {
my ($name, $inpath) = @_;
open INFILE, "<$inpath" or die "Failed to open $inpath: $!";
my $comment = 0;
my $line;
my ($calls, $callbacks, $refs) = (0, 0, 0);
my $call_re = qr/\b$name\b\s*\([^{]*$/;
my $callback_re = qr{\b$name\s*[,)]};
my $ref_re = qr{=\s*\b$name\b(?!\s*\()};
my $body = 0;
while (my $line = <INFILE>) {
chomp $line;
while ($line =~ /{/g) {
++$body;
}
while ($line =~ /}/g) {
--$body;
}
($line, $comment) = strip_comments($line, $comment);
++ $calls if $body && $line =~ /$call_re/;
++ $callbacks if $body && $line =~ /$callback_re/;
++ $refs if $line =~ /$ref_re/;
}
close INFILE;
return ($calls, $callbacks, $refs);
}
sub process_function {
my ($c_name, $static, $func, $inpath) = @_;
my ($ext_calls, $ext_files, $scm_calls, $scm_files, $ui_calls, $ui_files, $not_used);
my $gobject_re = qr/_(?:init|constructor|dispose|finalize|[sg]et_property)$/;
if ($c_name =~ /$gobject_re/) {
print OUTFH "/* $c_name\n";
print OUTFH join("\n", @$func);
print OUTFH "*/\n";
goto NO_USAGE;
}
unless ($static) {
($ext_calls, $ext_files) = search_external($c_name, $inpath);
($scm_calls, $scm_files) = search_scheme($c_name);
($ui_calls, $ui_files) = search_glade($c_name);
}
my ($local_calls, $local_callbacks, $local_refs) = search_local($c_name, $inpath);
unless ($ext_calls || $scm_calls || $ui_calls) {
my $local_use = $local_calls + $local_callbacks + $local_refs;
print OUTFH "// Make Static\n" if !$static && $local_use > 1;
unless ($local_use) {
print OUTFH "// Not Used\n";
$not_used = 1;
}
print OUTFH "/* $c_name\n";
print OUTFH join("\n", @$func);
print OUTFH "// Local: $local_calls:$local_callbacks:$local_refs\n";
print OUTFH "*/\n";
} else {
print OUTFH "/* $c_name\n";
print OUTFH join("\n", @$func);
print OUTFH "// ";
print OUTFH "External: 0\n" if $ext_calls + $scm_calls + $ui_calls == 0;
print OUTFH "C: $ext_calls " if $ext_calls > 0;
print OUTFH "in $ext_files " if $ext_calls > 1;
print OUTFH "SCM: $scm_calls " if $scm_calls > 0;
print OUTFH "in $scm_files" if $scm_calls > 1;
print OUTFH "UI: $ui_calls " if $ui_calls > 0;
print OUTFH "in $ui_files" if $ui_calls > 1;
print OUTFH " Local: $local_calls:$local_callbacks:$local_refs\n";
print OUTFH "*/\n";
}
NO_USAGE:
unless ($not_used) {
my $test_func = "test_$c_name";
my $test_name = $c_name;
$test_name =~ tr/_/ /;
print OUTFH "/* static void\n";
print OUTFH "test_$c_name (Fixture *fixture, gconstpointer pData)\n";
print OUTFH "{\n";
print OUTFH "}*/\n";
return "// GNC_TEST_ADD (suitename, \"$test_name\", Fixture, NULL, setup, $test_func, teardown);";
}
return;
}
sub print_preamble {
my ($infile, $author) = @_;
my ($gnuemail) = ('[email protected]');
my $inName = substr($infile, 0, index($infile, "."));
my $inname = lc $inName;
my @indirs = File::Spec->splitdir($indir);
my @date = localtime(time());
my $year = $date[5] + 1900;
delete $indirs[-1];
$inname =~ tr/-/_/;
$indirs[0] = "";
eval{
$indir = File::Spec->catfile((@indirs, $inName));
};
die "Catdir Failed $@" if $@;
print OUTFH <<EOF;
/********************************************************************
* $outfile: GLib g_test test suite for $infile. *
* Copyright $year $author *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, you can retrieve it from *
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html *
* or contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
* Boston, MA 02110-1301, USA $gnuemail *
********************************************************************/
#include <config.h>
#include <string.h>
#include <glib.h>
#include <unittest-support.h>
/* Add specific headers for this class */
static const gchar *suitename = "$indir";
void test_suite_$inname ( void );
EOF
return $inname;
}
=head1 NAME
make_testfile
=head1 SYNOPSIS
make_testfile "Author Name <[email protected]>" path/to/input [path/to/output]
=head1 SUMMARY
Creates template unit test files from C source files. The default
output file is utest-filename in a subdirectory named "test". For
example, if the input file is src/engine/Account.c, the default output
file will be src/engine/test/utest-Account.c.
The program scans the input file to find function signatures. Each
function signature will generate a comment with the function's
signature and the number of places that the function is called in C
and Scheme incantations (Scheme calls are assumed to be the same
function name with underscores replaced with dashes. The program
doesn't look at SWIG files to find aliases.)
The program attempts to determine each function's usage: All other C
and Scheme files in the source tree are searched for uses of the
function unless the function is marked "static". The function name is
mangled to replaces underscores with hyphens for searching Scheme
files. The input file is also searched for additional calls or
assignments to the function, and recursion is excepted.
A function for which no calls are found is marked "Not Used". A global
(i.e. not static) function with only local calls or assignments is
marked "should be static". GObject special functions (gnc_foo_init,
gnc_foo_class_init, gnc_foo_constructor, gnc_foo_dispose,
gnc_foo_finalize, gnc_foo_get_property, gnc_foo_set_property) are not
searched for.
After scanning, the program will add a comment to the outfile with the
function's signature and the results of the usage scan, followed by a
commented-out test function template (unless the function is found to
have no usage). After all of the functions are written out, the
program will finish by writing a test-suite function containing
commented-out macros invoking each of the skeleton test functions.
=cut