miyagawa / remedie

perl based pluggable media center application

This URL has Read+Write access

remedie / t / TestPlagger.pm
100644 365 lines (260 sloc) 8.652 kb
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
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package t::TestPlagger;
use Config;
use FindBin;
use File::Basename;
use File::Spec;
use Test::Base -Base;
use URI::Escape ();
use Plagger;
use Remedie::Log;
use YAML::XS;
 
our @EXPORT = qw(test_requires test_requires_network test_requires_command test_plugin_deps
run_eval_expected run_eval_expected_with_capture
slurp_file file_contains file_doesnt_contain);
 
our($BaseDir, $BaseDirURI);
{
    my @path = File::Spec->splitdir($FindBin::Bin);
    while (defined(my $dir = pop @path)) {
        if ($dir eq 't') {
            $BaseDir = File::Spec->catfile(@path);
            $BaseDirURI = join "/", map URI::Escape::uri_escape($_), @path;
            last;
        }
    }
}
 
=item test_requires
 
Checks to see if the module can be loaded.
 
test_requires("Your::Momma");
test_requires("Your::Momma",3.141); # version 3.141 or later
 
If this fails rather than failing tests this B<skips all tests>.
 
=cut
 
sub test_requires() {
    my($mod, $ver) = @_;
 
    if ($ver) {
        eval qq{use $mod $ver};
    } else {
        eval qq{use $mod};
    }
 
    if ($@) {
        if ($@ =~ /^Can't locate/) {
            plan skip_all => "Test requires module '$mod' but it's not found";
        }
        else {
            plan skip_all => "$@";
        }
    }
}
 
=item has_network($spec)
 
Returns true if and only if the specified port can be established. The
spec should be of the form:
 
hostname:port
 
e.g.
 
plagger.org:80
 
This function always returns false immediatly without connecting to the
network if the enviroment varible C<NO_NETWORK> has been set.
 
=cut
 
sub has_network() {
    my $host = shift;
    return if $ENV{NO_NETWORK};
 
    require IO::Socket::INET;
    my $conn = IO::Socket::INET->new(PeerAddr => $host, Timeout => 15);
    defined $conn;
}
 
=item test_requires_network
 
This function skips all tests if the network is not reachable, e.g.
 
# can I reach google's web site?
test_requires_network();
# can I reach plagger's web site?
test_requires_network("plagger.org")
# can I reach a different port?
test_requires_network("gmail.com:443");
 
=cut
 
sub test_requires_network() {
    my $host = shift || 'www.google.com:80';
       $host .= ":80" if $host !~ /:/;
 
    unless (has_network($host)) {
        plan skip_all => "Test requires network($host) which is not available now.";
    }
}
 
=item test_requires_command($command)
 
This function skips all tests if the given command
doesn't exist in your path (i.e. in the dirs in the PATH enviroment
variable.)
 
=cut
 
sub test_requires_command() {
    my $command = shift;
    for my $path (split /$Config::Config{path_sep}/, $ENV{PATH}) {
        if (-e File::Spec->catfile($path, $command) && -x _) {
            return 1;
        }
    }
    plan skip_all => "Test requires '$command' command but it's not found";
}
 
=item test_plugin_deps
 
This function skips all tests if the module's requirements
(modules, versions, platforms, bundles) aren't installed.
 
If you pass an argument then it will check the requirement
for that module:
 
# this will check the Foo-Bar.yaml file for deps
test_plugin_deps("Foo::Bar");
 
Called with no arguments it works out magically the name of
the plugin based on the directory the test file is located
in.
 
# in the Foo-Bar/wobble.t file
# this will check the Foo-Bar.yaml file for deps
test_plugin_deps();
The requirements are defined in YAML files located
in the C<deps> directory inside the Plagger directory. A typical
YAML file looks like this:
 
name: Publish::Speech::MacOSX
author: Ryo Okamoto
platform: darwin
depends:
Mac::Files: 0
Mac::Speech: 0
 
Or
 
name: Subscription::Bookmarks::Mozilla
author: youpy
bundles:
- Subscription::XPath
 
=cut
 
sub test_plugin_deps() {
    my($mod, $no_warning) = @_;
    $mod ||= File::Basename::basename($FindBin::Bin);
    $mod =~ s!::!-!g;
 
    my $file = File::Spec->catfile( $BaseDir, "deps", "$mod.yaml" );
    unless (-e $file) {
# warn "Can't find deps file for $mod" unless $no_warning;
        return;
    }
 
    my $meta = eval { YAML::XS::LoadFile($file) } or die "reading $file failed:\n$@";
 
    if ($meta->{platform} && $meta->{platform} ne $^O) {
        plan skip_all => "Test requires to be run on '$meta->{platform}'";
    }
 
    for my $plugin (@{ $meta->{bundles} || [] }) {
        $plugin =~ s/::/-/g;
        test_plugin_deps($plugin, 1);
    }
 
    while (my($mod, $ver) = each %{$meta->{depends} || {}}) {
        test_requires($mod, $ver);
    }
}
 
=item run_eval_expected
 
Add a new test run across all blocks to check that
the expected blocks can be evaled succesfully. During
these evaluations the expected input is availible from
C<$context>.
 
One extra test failure will happen per block that's expected
output throws an error when evaluated. No successes are
generated by this code, but the expected code may in
turn generate many sucesses or failures.
 
=cut
 
sub run_eval_expected {
    run {
        my $block = shift;
        
        # context is being pulled out here so that
        # the eval box can see it
        my $context = $block->input; # it's not always true
        
        eval $block->expected;
        fail $@ if $@;
    };
}
 
=item run_eval_expected_with_capture
 
Add new test run across all blocks to check that
the expected blocks can be evaled succesfully. During
these evaluations the expected input is availible from
C<$context>, and warnings created by the filters
are avalible from C<$warnings>.
 
=cut
 
sub run_eval_expected_with_capture {
    filters_delay;
    for my $block (blocks) {
      
        # capture all the warnings from the filters
        # this is often used in the tests as a way to find
        # out what has happened (e.g. the Growl plugin)
        my $warnings;
        {
            local $SIG{__WARN__} = sub { $warnings .= "@_" };
            $block->run_filters;
        }
        
        # context is being pulled out here so that
        # the eval box can see it
        my $context = $block->input;
        
        eval $block->expected;
        fail $@ if $@;
    }
}
 
=item slurp_file($filename)
 
Returns the contents of the file, as a single scalar
 
=cut
 
sub slurp_file() {
    my $file = shift;
    open my $fh, $file or return;
    return join '', <$fh>;
}
 
=item file_contains($filename, $regexp)
 
Test if the file (specified by filename) matches the passed regexp.
 
=cut
 
sub file_contains() {
    my($file, $pattern) = @_;
 
    like slurp_file($file), $pattern;
}
 
=item file_doesnt_contains($filename, $regexp)
 
Test the file (specified by filename) doesnt matches the passed regexp.
If the file doesn't exist, this test will fail.
 
=cut
 
sub file_doesnt_contain() {
    my($file, $pattern) = @_;
    $pattern = qr/\Q$pattern\E/ unless ref $pattern;
 
    my $content = slurp_file($file) or return fail("$file: $!");
    unlike $content, $pattern;
}
 
package t::TestPlagger::Filter;
use Test::Base::Filter -base;
use File::Temp ();
 
 
=over
 
=item interpolate
 
Filter that replaces scalar values of the type
 
$foo
$foo::bar
 
With their actual values. But don't do backslash escaped things like
 
\$foo
 
Note that you can't do this either:
 
${foo}
 
Meaning you can't use $foo like so:
 
BAR$fooHELLO
=cut
 
sub interpolate {
    my $stuff = shift;
    
    # interpolate in $foo::bar to their values in the string
    # (but not \$foo::bar)
    $stuff =~ s/(?<!\\) # check there's no backslash before this
(\$[\w\:]+(?:[\{\[]\w+[\]\}])?) # look for a $var possibly with packages
/$1/eegx; # replace it with its value
 
    $stuff =~ s/\\\$/\$/g; # turn the escaped \$ into $
    $stuff;
}
 
=item config
 
Filter that configures plagger based on the YAML passed in.
 
=cut
 
sub config {
    my $yaml = shift;
    
    # replace $foo values with their actual values
    $yaml = $self->interpolate($yaml);
 
    # set sane defaults for testing
    utf8::encode($yaml);
    my $config = YAML::XS::Load($yaml);
    $config->{global}->{log}->{level} ||= 'error' unless $ENV{TEST_VERBOSE};
    $config->{global}->{assets_path} ||= File::Spec->catfile($t::TestPlagger::BaseDir, 'root');
    $config->{global}->{cache}->{base} ||= File::Temp::tempdir(CLEANUP => 1);
 
    Plagger->bootstrap(config => $config);
}
 
=item output_file
 
Reads the file who's filename is in $main::output and returns it (failing on problems)
 
=cut
 
sub output_file {
    my $output = $main::output or die "\$main::output is undefined";
    open my $fh, $output or return ::fail("$output: $!");
    return join '', <$fh>;
}
 
1;