line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package EntityModel::Test::Storage; |
2
|
|
|
|
|
|
|
{ |
3
|
|
|
|
|
|
|
$EntityModel::Test::Storage::VERSION = '0.102'; |
4
|
|
|
|
|
|
|
} |
5
|
|
|
|
|
|
|
use EntityModel::Class { |
6
|
1
|
|
|
|
|
9
|
_isa => [qw(Exporter)], |
7
|
1
|
|
|
1
|
|
24508
|
}; |
|
1
|
|
|
|
|
75556
|
|
8
|
1
|
|
|
1
|
|
1958
|
no if $] >= 5.017011, warnings => "experimental::smartmatch"; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
6
|
|
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
=head1 NAME |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
EntityModel::Test::Storage - tests for L and subclasses |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
=head1 VERSION |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
version 0.102 |
17
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
=head1 SYNOPSIS |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
use EntityModel::Test::Storage; |
21
|
|
|
|
|
|
|
storage_ok('EntityModel::Storage::Perl', '::Perl subclass works'); |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
=head1 DESCRIPTION |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
Provides functions for testing L subclasses. |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
=cut |
28
|
|
|
|
|
|
|
|
29
|
1
|
|
|
1
|
|
61
|
use Test::Builder; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
17
|
|
30
|
1
|
|
|
1
|
|
5
|
use Module::Load; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
6
|
|
31
|
1
|
|
|
1
|
|
782
|
use CPS qw(kseq); |
|
1
|
|
|
|
|
5459
|
|
|
1
|
|
|
|
|
56
|
|
32
|
1
|
|
|
1
|
|
720
|
use EntityModel; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
5
|
|
33
|
|
|
|
|
|
|
|
34
|
1
|
|
|
|
|
1738
|
use constant STORAGE_METHODS => qw( |
35
|
|
|
|
|
|
|
new |
36
|
|
|
|
|
|
|
register |
37
|
|
|
|
|
|
|
apply_model |
38
|
|
|
|
|
|
|
apply_model_and_schema |
39
|
|
|
|
|
|
|
read |
40
|
|
|
|
|
|
|
create |
41
|
|
|
|
|
|
|
store |
42
|
|
|
|
|
|
|
remove |
43
|
|
|
|
|
|
|
find |
44
|
|
|
|
|
|
|
adjacent |
45
|
|
|
|
|
|
|
prev |
46
|
|
|
|
|
|
|
next |
47
|
|
|
|
|
|
|
outer |
48
|
|
|
|
|
|
|
first |
49
|
|
|
|
|
|
|
last |
50
|
1
|
|
|
1
|
|
48
|
); |
|
1
|
|
|
|
|
3
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
=head1 EXPORTS |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Since this is a test class, functions are exported automatically |
55
|
|
|
|
|
|
|
to match behaviour of other test modules such as L. |
56
|
|
|
|
|
|
|
To disable this, pass an empty list on the C |
57
|
|
|
|
|
|
|
use C instead: |
58
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
use EntityModel::Test::Storage (); |
60
|
|
|
|
|
|
|
EntityModel::Test::Storage::storage_ok(...); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
=cut |
63
|
|
|
|
|
|
|
|
64
|
|
|
|
|
|
|
our @EXPORT = qw( |
65
|
|
|
|
|
|
|
storage_ok |
66
|
|
|
|
|
|
|
storage_methods_ok |
67
|
|
|
|
|
|
|
); |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=head1 FUNCTIONS |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=cut |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=head2 storage_ok |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Runs all available tests (including attempting to load the module) and returns the usual |
76
|
|
|
|
|
|
|
L ok/fail response. |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=cut |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
sub storage_ok { |
81
|
1
|
|
|
1
|
1
|
12
|
my $class = shift; |
82
|
1
|
|
50
|
|
|
5
|
my $opt = shift || []; |
83
|
1
|
|
33
|
|
|
12
|
my $msg = shift || "$class is a valid, working EntityModel::Storage (sub)class"; |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
# First we need to be able to load our module |
86
|
|
|
|
|
|
|
try { |
87
|
1
|
|
|
|
|
9
|
Module::Load::load($class); |
88
|
1
|
|
|
|
|
2
|
} catch { |
89
|
1
|
|
|
|
|
118
|
return _report_fail($msg, $_); |
90
|
|
|
|
|
|
|
}; |
91
|
|
|
|
|
|
|
|
92
|
0
|
0
|
|
|
|
0
|
$class->isa('EntityModel::Storage') or return _report_fail($msg, 'is not an EntityModel::Storage (sub)class'); |
93
|
0
|
0
|
|
|
|
0
|
_methods_ok($class, $msg) or return; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
# Abstract base class won't work very well for 'real' model handling, so skip that |
96
|
|
|
|
|
|
|
|
97
|
0
|
0
|
|
|
|
0
|
unless($class eq 'EntityModel::Storage') { |
98
|
0
|
0
|
|
|
|
0
|
_simple_model($class, $opt, $msg) or return; |
99
|
|
|
|
|
|
|
} |
100
|
|
|
|
|
|
|
|
101
|
0
|
|
|
|
|
0
|
return _report_pass($msg); |
102
|
|
|
|
|
|
|
} |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
=head2 storage_methods_ok |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Check whether the expected methods are present. Requires the class to be loaded first. |
107
|
|
|
|
|
|
|
|
108
|
|
|
|
|
|
|
=cut |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
sub storage_methods_ok { |
111
|
1
|
|
|
1
|
1
|
8
|
my $class = shift; |
112
|
1
|
|
50
|
|
|
3
|
my $opt = shift || []; |
113
|
1
|
|
33
|
|
|
7
|
my $msg = shift || "$class has all the required methods"; |
114
|
1
|
0
|
|
|
|
4
|
return 0 unless _methods_ok($class, $msg); |
115
|
0
|
|
|
|
|
0
|
return _report_pass($msg); |
116
|
|
|
|
|
|
|
} |
117
|
|
|
|
|
|
|
|
118
|
|
|
|
|
|
|
=head2 _methods_ok |
119
|
|
|
|
|
|
|
|
120
|
|
|
|
|
|
|
Internal helper function to report whether the expected methods are present for the subclass. |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
=cut |
123
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub _methods_ok { |
125
|
1
|
|
|
1
|
|
2
|
my $class = shift; |
126
|
1
|
|
|
|
|
2
|
my $msg = shift; |
127
|
|
|
|
|
|
|
|
128
|
1
|
|
|
|
|
1
|
my %failed; |
129
|
1
|
|
|
|
|
5
|
foreach my $method (STORAGE_METHODS) { |
130
|
|
|
|
|
|
|
try { |
131
|
1
|
|
|
|
|
24
|
$class->can($method); |
132
|
1
|
0
|
0
|
|
|
2
|
} catch { |
133
|
1
|
|
|
|
|
182
|
$failed{$method} = $_; |
134
|
|
|
|
|
|
|
} or $failed{$method} ||= 'not available'; |
135
|
|
|
|
|
|
|
} |
136
|
0
|
0
|
|
|
|
0
|
if(keys %failed) { |
137
|
0
|
|
|
|
|
0
|
return _report_fail($msg, join "\n", map { "Could not $class->$_ because: " . $failed{$_} } sort keys %failed); |
|
0
|
|
|
|
|
0
|
|
138
|
|
|
|
|
|
|
} |
139
|
0
|
|
|
|
|
0
|
return 1; |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
=head2 _simple_model |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=cut |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
sub _simple_model { |
147
|
0
|
|
|
0
|
|
0
|
my $class = shift; |
148
|
0
|
|
|
|
|
0
|
my $opt = shift; |
149
|
0
|
|
|
|
|
0
|
my $msg = shift; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# Bring in a simple model - if this fails then it's not the storage class' fault so die() rather than marking as failure |
152
|
0
|
0
|
|
|
|
0
|
my $model = EntityModel->new->load_from( |
153
|
|
|
|
|
|
|
Perl => { |
154
|
|
|
|
|
|
|
"name" => "mymodel", |
155
|
|
|
|
|
|
|
"entity" => [ { |
156
|
|
|
|
|
|
|
"name" => "thing", |
157
|
|
|
|
|
|
|
"primary" => "id", |
158
|
|
|
|
|
|
|
"field" => [ |
159
|
|
|
|
|
|
|
{ "name" => "id", "type" => "int" }, |
160
|
|
|
|
|
|
|
{ "name" => "name", "type" => "varchar" } |
161
|
|
|
|
|
|
|
] |
162
|
|
|
|
|
|
|
}, { |
163
|
|
|
|
|
|
|
"name" => "other", |
164
|
|
|
|
|
|
|
"primary" => "id", |
165
|
|
|
|
|
|
|
"field" => [ |
166
|
|
|
|
|
|
|
{ "name" => "id", "type" => "int" }, |
167
|
|
|
|
|
|
|
{ "name" => "extra", "type" => "varchar" } |
168
|
|
|
|
|
|
|
] |
169
|
|
|
|
|
|
|
} ] |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
) or die "Model creation failed"; |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
# Now we try to apply the storage model |
174
|
0
|
0
|
|
|
|
0
|
$model->add_storage($class => $opt) or die "Failed to add storage"; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
# Sanity check that we end up with single storage item of the expected class |
177
|
0
|
|
|
|
|
0
|
my @storage = $model->storage->list; |
178
|
0
|
0
|
|
|
|
0
|
return _report_fail($msg, "expected 1 storage item, found " . scalar(@storage)) unless @storage == 1; |
179
|
0
|
0
|
|
|
|
0
|
return _report_fail($msg, "unexpected class found, wanted $class but had " . join(',', map ref, @storage)) if grep { ref($_) ne $class } @storage; |
|
0
|
|
|
|
|
0
|
|
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
# Now the model has been applied, we'll try to do some simple tests directly against storage: |
182
|
|
|
|
|
|
|
# first we'll create an entry |
183
|
0
|
|
|
|
|
0
|
my ($storage) = @storage; |
184
|
0
|
|
|
|
|
0
|
my ($thing) = grep { $_->name eq 'thing' } $model->entity->list; |
|
0
|
|
|
|
|
0
|
|
185
|
0
|
0
|
|
|
|
0
|
die "no thing" unless $thing; |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
# Support failure passthrough in our continuations |
188
|
0
|
|
|
|
|
0
|
my $failed = 0; |
189
|
|
|
|
|
|
|
my $fail = sub { |
190
|
0
|
|
|
0
|
|
0
|
my ($err, $next) = @_; |
191
|
|
|
|
|
|
|
# Bail out immediately if we're already in failure state. |
192
|
0
|
0
|
0
|
|
|
0
|
$next->() if $failed && $next; |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# This would be a most lamentable state of affairs |
195
|
0
|
0
|
|
|
|
0
|
die "Failed already and no continuation, help!" if $failed; |
196
|
|
|
|
|
|
|
|
197
|
0
|
|
|
|
|
0
|
$failed = 1; |
198
|
0
|
|
|
|
|
0
|
_report_fail($msg, $err); |
199
|
0
|
0
|
|
|
|
0
|
$next->() if $next; |
200
|
0
|
|
|
|
|
0
|
}; |
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# Take advantage of CPS to avoid excessive indentation |
203
|
0
|
|
|
|
|
0
|
my ($id, $data); |
204
|
|
|
|
|
|
|
kseq(sub { |
205
|
|
|
|
|
|
|
# First we create a simple entity instance |
206
|
0
|
|
|
0
|
|
0
|
my $next = pop; |
207
|
|
|
|
|
|
|
$storage->create( |
208
|
|
|
|
|
|
|
entity => $thing, |
209
|
|
|
|
|
|
|
data => { |
210
|
|
|
|
|
|
|
name => 'Test name', |
211
|
|
|
|
|
|
|
}, |
212
|
|
|
|
|
|
|
on_complete => sub { |
213
|
0
|
|
|
|
|
0
|
$id = shift; |
214
|
0
|
|
|
|
|
0
|
$next->(); |
215
|
|
|
|
|
|
|
}, |
216
|
|
|
|
|
|
|
on_fail => sub { |
217
|
0
|
|
|
|
|
0
|
$fail->("Something failed", $next); |
218
|
|
|
|
|
|
|
} |
219
|
0
|
0
|
|
|
|
0
|
) or $fail->("->create returned false", $next); |
220
|
|
|
|
|
|
|
}, sub { |
221
|
|
|
|
|
|
|
# Next we check, then read it back |
222
|
0
|
|
|
0
|
|
0
|
my $next = pop; |
223
|
0
|
0
|
|
|
|
0
|
$next->() if $failed; |
224
|
0
|
0
|
|
|
|
0
|
$fail->("no ID assigned", $next) unless defined $id; |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
$storage->read( |
227
|
|
|
|
|
|
|
entity => $thing, |
228
|
|
|
|
|
|
|
id => $id, |
229
|
|
|
|
|
|
|
on_complete => sub { |
230
|
0
|
|
|
|
|
0
|
$data = shift; |
231
|
0
|
|
|
|
|
0
|
$next->(); |
232
|
|
|
|
|
|
|
}, |
233
|
|
|
|
|
|
|
on_fail => sub { |
234
|
0
|
|
|
|
|
0
|
$fail->("Something failed", $next); |
235
|
|
|
|
|
|
|
} |
236
|
0
|
0
|
|
|
|
0
|
) or $fail->("->read returned false", $next); |
237
|
|
|
|
|
|
|
}, sub { |
238
|
|
|
|
|
|
|
# Verify that we read back what we wrote originally |
239
|
0
|
|
|
0
|
|
0
|
my %read = %$data; |
240
|
0
|
0
|
|
|
|
0
|
$fail->("wrong keys returned: " . join ',', keys %read) unless [sort keys %read] ~~ [qw(id name)]; |
241
|
0
|
0
|
|
|
|
0
|
$fail->("wrong data for name - returned: " . $read{name}) unless $read{name} eq 'Test name'; |
242
|
0
|
|
|
|
|
0
|
}); |
243
|
0
|
0
|
|
|
|
0
|
return 0 if $failed; |
244
|
0
|
|
|
|
|
0
|
return 1; |
245
|
|
|
|
|
|
|
} |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
=head2 _report_status |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Internal helper function to report pass/fail via L. |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
sub _report_status { |
254
|
1
|
|
|
1
|
|
2
|
my $ok = shift; |
255
|
1
|
|
|
|
|
3
|
my $msg = shift; |
256
|
1
|
|
|
|
|
2
|
my $diag = shift; |
257
|
|
|
|
|
|
|
|
258
|
1
|
|
|
|
|
12
|
my $test = Test::Builder->new; |
259
|
1
|
|
|
|
|
19
|
$test->ok($ok, $msg); |
260
|
1
|
50
|
|
|
|
1206
|
$test->diag($diag) if defined $diag; |
261
|
1
|
|
|
|
|
5
|
return $ok; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
0
|
|
|
0
|
|
0
|
sub _report_pass { _report_status(1, @_) } |
265
|
|
|
|
|
|
|
|
266
|
1
|
|
|
1
|
|
6
|
sub _report_fail { _report_status(0, @_) } |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
1; |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
__END__ |