File Coverage

blib/lib/App/Prove/Elasticsearch/Planner/Default.pm
Criterion Covered Total %
statement 127 152 83.5
branch 45 62 72.5
condition 22 33 66.6
subroutine 14 17 82.3
pod 9 9 100.0
total 217 273 79.4


line stmt bran cond sub pod time code
1             # ABSTRACT: Index, create and retrieve test plans for use later
2             # PODNAME: App::Prove::Elasticsearch::Planner::Default
3              
4             package App::Prove::Elasticsearch::Planner::Default;
5             $App::Prove::Elasticsearch::Planner::Default::VERSION = '0.001';
6 2     2   104858 use strict;
  2         11  
  2         43  
7 2     2   8 use warnings;
  2         3  
  2         42  
8              
9 2     2   321 use App::Prove::Elasticsearch::Utils();
  2         5  
  2         28  
10              
11 2     2   365 use Search::Elasticsearch();
  2         32899  
  2         30  
12 2     2   10 use File::Basename();
  2         3  
  2         19  
13 2     2   8 use Cwd();
  2         4  
  2         42  
14 2     2   9 use List::Util 1.45 qw{uniq};
  2         27  
  2         2847  
15              
16             our $index = 'testplans';
17             our $e; # for caching
18             our $last_id;
19              
20             our $max_query_size = 1000;
21              
22             sub check_index {
23 5     5 1 2385 my ($conf) = @_;
24              
25 5 100       19 my $port = $conf->{'server.port'} ? ':' . $conf->{'server.port'} : '';
26 5 100       27 die "server must be specified" unless $conf->{'server.host'};
27 3 100       12 die("port must be specified") unless $port;
28 2         3 my $serveraddress = "$conf->{'server.host'}$port";
29              
30 2   66     11 $e //= Search::Elasticsearch->new(
31             nodes => $serveraddress,
32             request_timeout => 30
33             );
34              
35             #XXX for debugging
36             #$e->indices->delete( index => $index );
37              
38 2 100       13 if (!$e->indices->exists(index => $index)) {
39 1         6 $e->indices->create(
40             index => $index,
41             body => {
42             index => {
43             number_of_shards => "3",
44             number_of_replicas => "2",
45             similarity => {default => {type => "classic"}}
46             },
47             analysis => {
48             analyzer => {
49             default => {
50             type => "custom",
51             tokenizer => "whitespace",
52             filter => [
53             'lowercase', 'std_english_stop', 'custom_stop'
54             ]
55             }
56             },
57             filter => {
58             std_english_stop => {
59             type => "stop",
60             stopwords => "_english_"
61             },
62             custom_stop => {
63             type => "stop",
64             stopwords => [ "test", "ok", "not" ]
65             }
66             }
67             },
68             mappings => {
69             testplan => {
70             properties => {
71             id => {type => "integer"},
72             created => {
73             type => "date",
74             format => "yyyy-MM-dd HH:mm:ss"
75             },
76             creator => {
77             type => "text",
78             analyzer => "default",
79             fielddata => "true",
80             term_vector => "yes",
81             similarity => "classic",
82             fields => {keyword => {type => "keyword"}}
83             },
84             version => {
85             type => "text",
86             analyzer => "default",
87             fielddata => "true",
88             term_vector => "yes",
89             similarity => "classic",
90             fields => {keyword => {type => "keyword"}}
91             },
92             platforms => {
93             type => "text",
94             analyzer => "default",
95             fielddata => "true",
96             term_vector => "yes",
97             similarity => "classic",
98             fields => {keyword => {type => "keyword"}}
99             },
100             tests => {
101             type => "text",
102             analyzer => "default",
103             fielddata => "true",
104             term_vector => "yes",
105             similarity => "classic",
106             fields => {keyword => {type => "keyword"}}
107             },
108             pairwise => {type => "boolean"},
109             name => {
110             type => "text",
111             analyzer => "default",
112             fielddata => "true",
113             term_vector => "yes",
114             similarity => "classic",
115             fields => {keyword => {type => "keyword"}}
116             },
117             }
118             }
119             }
120             }
121             );
122 1         47 return 1;
123             }
124 1         13 return 0;
125             }
126              
127             sub find_test_paths {
128 0     0 1 0 my (@tests) = @_;
129 0         0 return map { "t/$_" } @tests;
  0         0  
130             }
131              
132             sub get_plan {
133 9     9 1 1127 my (%options) = @_;
134              
135 9 100       25 die "A version must be passed." unless $options{version};
136              
137             my %q = (
138             index => $index,
139             body => {
140             query => {
141             bool => {
142             must => [
143             {
144             match => {
145             version => $options{version},
146             }
147             },
148 8         39 ],
149             },
150             },
151             size => 1,
152             },
153             );
154              
155 2         7 push(@{$q{body}{query}{bool}{must}}, {match => {name => $options{name}}})
156 8 100       19 if $options{name};
157              
158 8         13 foreach my $plat (@{$options{platforms}}) {
  8         15  
159 4         4 push(@{$q{body}{query}{bool}{must}}, {match => {platforms => $plat}});
  4         12  
160             }
161              
162 8         24 my $docs = $e->search(%q);
163              
164             return 0
165             unless ref $docs eq 'HASH'
166             && ref $docs->{hits} eq 'HASH'
167 8 50 66     57 && ref $docs->{hits}->{hits} eq 'ARRAY';
      66        
168 7 100       10 return 0 unless scalar(@{$docs->{hits}->{hits}});
  7         18  
169 6         11 my $match = $docs->{hits}->{hits}->[0]->{_source};
170              
171             my @plats_match = (
172             (ref($match->{platforms}) eq 'ARRAY')
173 0         0 ? @{$match->{platforms}}
174             : ($match->{platforms})
175 6 50       15 );
176              
177             my $name_correct =
178 6   100     18 !$options{name} || ($match->{name} // '') eq ($options{name} // '');
179 6         13 my $version_correct = $match->{version} eq $options{version};
180 6         6 my $plats_size_ok = scalar(@plats_match) == scalar(@{$options{platforms}});
  6         8  
181             my $plats_are_same =
182             scalar(@plats_match) ==
183 6         10 scalar(uniq((@plats_match, @{$options{platforms}})));
  6         18  
184             my $plats_correct =
185 6   100     9 !scalar(@{$options{platforms}}) || ($plats_size_ok && $plats_are_same);
186              
187 6         11 $match->{id} = $docs->{hits}->{hits}->[0]->{_id};
188 6 100 66     36 return $match if ($name_correct && $version_correct && $plats_correct);
      100        
189              
190 3         15 return 0;
191             }
192              
193             sub get_plans {
194 0     0 1 0 my (%options) = @_;
195              
196 0 0       0 die "A version must be passed." unless $options{version};
197              
198 0         0 my %q = (
199             index => $index,
200             body => {
201             query => {
202             query_string => {
203             query => qq{version: "$options{version}"},
204             },
205             },
206             },
207             );
208              
209             $q{body}{query}{query_string}{query} .= qq{ AND name: "$options{name}" }
210 0 0       0 if $options{name};
211              
212 0         0 foreach my $plat (@{$options{platforms}}) {
  0         0  
213 0         0 $q{body}{query}{query_string}{query} .= qq{ AND platforms: "$plat" };
214             }
215 0         0 return App::Prove::Elasticsearch::Utils::do_paginated_query(
216             $e,
217             $max_query_size, %q
218             );
219             }
220              
221             sub get_plans_needing_work {
222 0     0 1 0 my (%options) = @_;
223              
224             die "Can't find plans needing work without case autodiscover configured!"
225 0 0       0 unless $options{searcher};
226              
227 0         0 my @plans;
228 0         0 my $docs = get_plans(%options);
229 0 0 0     0 return () unless ref $docs eq 'ARRAY' && scalar(@$docs);
230              
231 0         0 foreach my $doc (@$docs) {
232             next
233             unless ref $doc->{_source}->{tests} eq 'ARRAY'
234 0 0 0     0 && scalar(@{$doc->{_source}->{tests}});
  0         0  
235             my @tests = $options{searcher}
236 0         0 ->filter(find_test_paths(@{$doc->{_source}->{tests}}));
  0         0  
237 0         0 $doc->{_source}->{tests} = \@tests;
238 0 0       0 push(@plans, $doc->{_source}) if @tests;
239             }
240 0         0 return \@plans;
241             }
242              
243             sub get_plan_status {
244 1     1 1 597 my ($plan, $searcher) = @_;
245              
246 1 50       6 die "Can't discover plan status without case autodiscover configured!"
247             unless $searcher;
248             return $searcher->get_test_replay(
249             $plan->{version}, $plan->{platforms},
250 1         3 @{$plan->{tests}}
  1         3  
251             );
252             }
253              
254             sub add_plan_to_index {
255 5     5 1 1847 my ($plan) = @_;
256              
257 5 100       15 if ($plan->{noop}) {
258 1         117 print "Nothing to do!\n";
259 1         7 return 0;
260             }
261 4 100       12 return _update_plan($plan) if $plan->{update};
262              
263 3 100       16 die "check_index not run, ES object not defined!" unless $e;
264              
265 2         5 my $idx = App::Prove::Elasticsearch::Utils::get_last_index($e, $index);
266 2         7 $idx++;
267              
268 2         7 $e->index(
269             index => $index,
270             id => $idx,
271             type => 'testplan',
272             body => $plan,
273             );
274              
275 2         167 my $doc_exists =
276             $e->exists(index => $index, type => 'testplan', id => $idx);
277 2   50     14 my $pn = $plan->{'name'} // '';
278 2 100 66     10 if (!defined($doc_exists) || !int($doc_exists)) {
279 1         86 print "Failed to Index $pn, could find no record with ID $idx\n";
280 1         8 return 1;
281             }
282              
283 1         86 print "Successfully Indexed plan $pn with result ID $idx\n";
284 1         8 return 0;
285              
286             }
287              
288             sub _update_plan {
289 2     2   3445 my ($plan) = @_;
290              
291             #handle adding new tests, then subtract
292             my @tests_merged =
293 2         4 (@{$plan->{tests}}, @{$plan->{update}->{addition}->{tests}});
  2         4  
  2         5  
294             @tests_merged = grep {
295 2         5 my $subj = $_;
  6         8  
296 6         7 !grep { $_ eq $subj } @{$plan->{update}->{subtraction}->{tests}}
  6         15  
  6         9  
297             } @tests_merged;
298              
299             my $res = $e->update(
300             index => $index,
301             id => $plan->{id},
302 2         8 type => 'testplan',
303             body => {
304             doc => {
305             tests => \@tests_merged,
306             },
307             }
308             );
309              
310 2 50       94 print "Updated tests in plan #$plan->{id}\n" if $res->{result} eq 'updated';
311 2 100       5 if (!grep { $res->{result} eq $_ } qw{updated noop}) {
  4         11  
312 1         10 print
313             "Something went wrong associating cases to document $plan->{id}!\n$res->{result}\n";
314 1         6 return 1;
315             }
316 1         85 print "Successfully Updated plan #$plan->{id}\n";
317 1         11 return 0;
318             }
319              
320             sub make_plan {
321 3     3 1 1204 my (%options) = @_;
322 3 100       14 die "check_index not run, ES object not defined!" unless $e;
323              
324 2         9 my %out = %options;
325 2 100       6 $out{pairwise} = $out{pairwise} ? "true" : "false";
326 2         4 delete $out{show};
327 2         3 delete $out{prompt};
328 2         3 delete $out{allplatforms};
329 2         2 delete $out{exts};
330 2         3 delete $out{recurse};
331 2 100       5 delete $out{name} unless $out{name};
332              
333 2 100       3 $out{noop} = 1 unless scalar(@{$out{tests}});
  2         5  
334              
335 2         9 return \%out;
336             }
337              
338             sub make_plan_update {
339 3     3 1 1425 my ($existing, %out) = @_;
340 3 100       13 die "check_index not run, ES object not defined!" unless $e;
341              
342             #TODO be sure to do the right thing w pairwise testing (dole out tests appropriately)
343              
344 2         4 my $adds = {};
345 2         4 my $subs = {};
346 2         2 foreach my $okey (@{$out{tests}}) {
  2         5  
347 1         3 push(@{$adds->{tests}}, $okey)
348 2 100       3 if !grep { $_ eq $okey } @{$existing->{tests}};
  2         7  
  2         4  
349             }
350 2         46 foreach my $ekey (@{$existing->{tests}}) {
  2         7  
351 2 100       2 push(@{$subs->{tests}}, $ekey) if !grep { $_ eq $ekey } @{$out{tests}};
  1         3  
  2         8  
  2         3  
352             }
353              
354 2 50 66     10 if (!scalar(keys(%$adds)) && !scalar(keys(%$subs))) {
355 1         2 $existing->{noop} = 1;
356 1         5 return $existing;
357             }
358 1         17 $existing->{update} = {addition => $adds, subtraction => $subs};
359              
360 1         6 return $existing;
361             }
362              
363             1;
364              
365             __END__