File Coverage

blib/lib/VSGDR/TestScriptGen.pm
Criterion Covered Total %
statement 59 151 39.0
branch 0 36 0.0
condition 0 9 0.0
subroutine 20 27 74.0
pod n/a
total 79 223 35.4


line stmt bran cond sub pod time code
1             package VSGDR::TestScriptGen;
2            
3 1     1   13000 use strict;
  1         2  
  1         22  
4 1     1   3 use warnings;
  1         1  
  1         19  
5            
6 1     1   15 use 5.010;
  1         5  
7            
8 1     1   3 use List::Util qw(max);
  1         2  
  1         76  
9             #use List::MoreUtils;
10 1     1   458 use List::MoreUtils qw{firstidx} ;
  1         6747  
  1         4  
11 1     1   839 use POSIX qw(strftime);
  1         4357  
  1         3  
12 1     1   779 use Carp;
  1         1  
  1         43  
13 1     1   1405 use DBI;
  1         11213  
  1         47  
14 1     1   652 use Data::Dumper;
  1         6288  
  1         47  
15 1     1   452 use English;
  1         2480  
  1         5  
16 1     1   755 use IO::File ;
  1         6204  
  1         95  
17 1     1   5 use File::Basename;
  1         1  
  1         46  
18 1     1   459 use Try::Tiny;
  1         925  
  1         44  
19            
20 1     1   437 use VSGDR::UnitTest::TestSet::Test;
  1         1354  
  1         25  
21 1     1   477 use VSGDR::UnitTest::TestSet::Test::TestCondition;
  1         678  
  1         23  
22 1     1   413 use VSGDR::UnitTest::TestSet::Representation;
  1         3845  
  1         37  
23 1     1   438 use VSGDR::UnitTest::TestSet::Resx;
  1         8419  
  1         39  
24 1     1   5 use File::Basename;
  1         1  
  1         510  
25            
26            
27             =head1 NAME
28            
29             VSGDR::TestScriptGen - Unit test script support package for SSDT unit tests, Ded MedVed.
30            
31             =head1 VERSION
32            
33             Version 0.16
34            
35             =cut
36            
37             our $VERSION = '0.16';
38            
39            
40             sub databaseName {
41            
42 0     0     local $_ = undef ;
43            
44 0           my $dbh = shift ;
45            
46 0           my $sth2 = $dbh->prepare(databaseNameSQL());
47 0           my $rs = $sth2->execute();
48 0           my $res = $sth2->fetchall_arrayref() ;
49            
50 0           return $$res[0][0] ;
51            
52             }
53            
54             sub databaseNameSQL {
55            
56 0     0     return <<"EOF" ;
57            
58             select db_name()
59            
60             EOF
61            
62             }
63            
64             sub ExecSp {
65            
66 0     0     local $_ = undef ;
67            
68 0           my $dbh = shift ;
69            
70 0           my $sth2 = $dbh->prepare( ExecSpSQL());
71 0           my $rs = $sth2->execute();
72 0           my $res = $sth2->fetchall_arrayref() ;
73            
74 0 0         if ( scalar @{$res} ) { return $res ; } ;
  0            
  0            
75 0           return [] ;
76             }
77            
78            
79            
80             sub ExecSpSQL {
81            
82 0     0     return <<"EOF" ;
83            
84             ; with BASE as (
85             SELECT case when ROUTINE_TYPE = 'PROCEDURE' then cast([PARAMETER_NAME] + ' = ' + [PARAMETER_NAME] + case when PARAMETER_MODE = 'IN' then '' else ' OUTPUT' + CHAR(10) end as VARCHAR(MAX))
86             when ROUTINE_TYPE = 'FUNCTION' then cast([PARAMETER_NAME] as VARCHAR(MAX))
87             end as PARAMTER
88             -- cast([PARAMETER_NAME] + ' = ' + [PARAMETER_NAME] + case when PARAMETER_MODE = 'IN' then '' else ' OUTPUT' + CHAR(10) end as VARCHAR(MAX)) as PARAMTER
89             , cast([PARAMETER_NAME] + ' ' + case when USER_DEFINED_TYPE_CATALOG is not null then USER_DEFINED_TYPE_SCHEMA + '.' + USER_DEFINED_TYPE_NAME when P.DATA_TYPE in ('ntext','text') then 'varchar' when P.DATA_TYPE in ('image') then 'varbinary' else P.DATA_TYPE end +coalesce('('+case when P.CHARACTER_MAXIMUM_LENGTH = -1 or P.CHARACTER_MAXIMUM_LENGTH > 8000 then 'max' else cast(P.CHARACTER_MAXIMUM_LENGTH as varchar) end+')','') + CHAR(10) as VARCHAR(MAX)) as DECLARATION
90             , R.[SPECIFIC_CATALOG]
91             , R.[SPECIFIC_SCHEMA]
92             , R.[SPECIFIC_NAME]
93             , [ORDINAL_POSITION]
94             , [PARAMETER_MODE]
95             FROM [INFORMATION_SCHEMA].[PARAMETERS] P
96             JOIN INFORMATION_SCHEMA.ROUTINES R
97             on R.[SPECIFIC_NAME] = P.[SPECIFIC_NAME]
98             and R.[SPECIFIC_SCHEMA] = P.[SPECIFIC_SCHEMA]
99             and R.[SPECIFIC_CATALOG] = P.[SPECIFIC_CATALOG]
100             where 1=1
101             and ORDINAL_POSITION = 1
102             union all
103             select cast(PARAMTER + +char(10)+CHAR(9)+CHAR(9)+CHAR(9)+char(9)+CHAR(9)+CHAR(9)+CHAR(9)+CHAR(9)+',' + CHAR(9)+ CHAR(9) + case when ROUTINE_TYPE = 'PROCEDURE' then cast(N.[PARAMETER_NAME] + ' = ' + N.[PARAMETER_NAME] + case when N.PARAMETER_MODE = 'IN' then '' else ' OUTPUT' + CHAR(10) end as VARCHAR(MAX))
104             when ROUTINE_TYPE = 'FUNCTION' then cast(N.[PARAMETER_NAME] as VARCHAR(MAX))
105             end as VARCHAR(MAX)) as PARAMTER
106             --N.[PARAMETER_NAME] + ' = ' + N.[PARAMETER_NAME] + case when N.PARAMETER_MODE = 'IN' then '' else ' OUTPUT' + CHAR(10) end as varchar(max))
107             , cast(DECLARATION + CHAR(9)+',' + CHAR(9)+CHAR(9) + [PARAMETER_NAME] + ' ' + case when N.DATA_TYPE in ('ntext','text') then 'varchar' when N.DATA_TYPE in ('image') then 'varbinary' else N.DATA_TYPE end +coalesce('('+case when N.CHARACTER_MAXIMUM_LENGTH = -1 or N.CHARACTER_MAXIMUM_LENGTH > 8000 then 'max' else cast(N.CHARACTER_MAXIMUM_LENGTH as varchar) end +')','') + CHAR(10) as VARCHAR(MAX))
108             , N.[SPECIFIC_CATALOG]
109             , N.[SPECIFIC_SCHEMA]
110             , N.[SPECIFIC_NAME]
111             , N.[ORDINAL_POSITION]
112             , N.[PARAMETER_MODE]
113             from [INFORMATION_SCHEMA].[PARAMETERS] N
114             JOIN INFORMATION_SCHEMA.ROUTINES R
115             on R.[SPECIFIC_NAME] = N.[SPECIFIC_NAME]
116             and R.[SPECIFIC_SCHEMA] = N.[SPECIFIC_SCHEMA]
117             and R.[SPECIFIC_CATALOG] = N.[SPECIFIC_CATALOG]
118             join BASE B
119             on N.[SPECIFIC_NAME] = B.[SPECIFIC_NAME]
120             and N.[SPECIFIC_SCHEMA] = B.[SPECIFIC_SCHEMA]
121             and N.[SPECIFIC_CATALOG] = B.[SPECIFIC_CATALOG]
122             and N.ORDINAL_POSITION = B.ORDINAL_POSITION+1
123             )
124             , ALLL as (
125             select *
126             , ROW_NUMBER() over (partition by [SPECIFIC_CATALOG],[SPECIFIC_SCHEMA],[SPECIFIC_NAME] order by ORDINAL_POSITION DESC ) as RN
127             from BASE
128             )
129             , PARAMS as (
130             select * from ALLL where RN = 1
131             )
132             select '[' + R.SPECIFIC_SCHEMA + '].[' + R.SPECIFIC_NAME +']' as sp
133             , case when ROUTINE_TYPE = 'FUNCTION' and DATA_TYPE != 'TABLE'
134             then 'declare ' + coalesce(DECLARATION+char(9)+','+char(9)+char(9),'') + '\@RC ' + DATA_TYPE+coalesce('('+cast(CHARACTER_MAXIMUM_LENGTH as varchar)+')','')
135             else coalesce('declare ' + DECLARATION,'')
136             end as DECLARATION
137             , case when ROUTINE_TYPE = 'PROCEDURE' then 'execute [' + R.SPECIFIC_SCHEMA + '].[' + R.SPECIFIC_NAME + '] ' + coalesce(B.PARAMTER,'')
138             when ROUTINE_TYPE = 'FUNCTION' and DATA_TYPE = 'TABLE' then 'select * from [' + R.SPECIFIC_SCHEMA + '].[' + R.SPECIFIC_NAME + '](' + coalesce(B.PARAMTER,'') + ')'
139             when ROUTINE_TYPE = 'FUNCTION' and DATA_TYPE != 'TABLE' then 'select \@RC = [' + R.SPECIFIC_SCHEMA + '].[' + R.SPECIFIC_NAME + '](' + coalesce(B.PARAMTER,'') + ')'
140             else '-- unknown routine type'
141             end as sql
142             from INFORMATION_SCHEMA.ROUTINES R
143             LEFT JOIN PARAMS B
144             on R.[SPECIFIC_NAME] = B.[SPECIFIC_NAME]
145             and R.[SPECIFIC_SCHEMA] = B.[SPECIFIC_SCHEMA]
146             and R.[SPECIFIC_CATALOG] = B.[SPECIFIC_CATALOG]
147             where R.ROUTINE_TYPE in( 'PROCEDURE','FUNCTION')
148            
149            
150             EOF
151            
152             }
153            
154            
155             sub generateScripts {
156            
157 0     0     local $_ = undef;
158            
159 0           my $dbh = shift ;
160 0           my $dbh_typeinfo = shift ;
161 0           my $dirs = shift ;
162 0           my $file = shift ;
163 0           my $runChecks = shift ;
164            
165 0 0         croak "bad arg dbh" unless defined $dbh;
166 0 0         croak "bad arg dbh_typeinfo" unless defined $dbh_typeinfo;
167 0 0         croak "bad arg dirs" unless defined $dirs;
168             #croak "bad arg file" unless defined $file;
169 0 0         croak "bad arg runChecks" unless defined $runChecks;
170            
171 0           my $testSet = undef;
172 0 0         if ( defined $file ) {
173            
174 0           my %ValidParserMakeArgs = ( vb => "NET::VB"
175             , cs => "NET::CS"
176             , xls => "XLS"
177             , xml => "XML"
178             ) ;
179 0           my %ValidParserMakeArgs2 = ( vb => "NET2::VB"
180             , cs => "NET2::CS"
181             ) ;
182            
183             #my @validSuffixes = keys %ValidParserMakeArgs ;
184 0           my @validSuffixes = map { '.'.$_ } keys %ValidParserMakeArgs ;
  0            
185            
186 0           my $infile = $file;
187            
188 0           my($infname, $directories, $insfx) = fileparse($infile , @validSuffixes);
189 0 0         croak 'Invalid input file' unless defined $insfx ;
190 0           $insfx = lc $insfx ;
191 0           $insfx = substr $insfx,1;
192            
193             ### Validate parameters
194 0 0         die 'Invalid input file' unless exists $ValidParserMakeArgs{$insfx} ;
195            
196             ### Build parsers
197            
198 0           my %Parsers = () ;
199 0           $Parsers{${insfx}} = VSGDR::UnitTest::TestSet::Representation->make( { TYPE => $ValidParserMakeArgs{${insfx}} } );
200             # if input is in a .net language, add in a .net2 parser to the list
201 0 0   0     if ( firstidx { $_ eq ${insfx} } ['cs','vb'] != -1 ) {
  0            
202 0           $Parsers{"${insfx}2"} = VSGDR::UnitTest::TestSet::Representation->make( { TYPE => $ValidParserMakeArgs2{${insfx}} } );
203             }
204            
205             ### Deserialise tests
206 0           eval {
207 0           $testSet = $Parsers{$insfx}->deserialise($infile);
208             } ;
209 0 0         if ( not defined $testSet ) {
210 0 0         if ( exists $Parsers{"${insfx}2"}) {
211 0           eval {
212 0           $testSet = $Parsers{"${insfx}2"}->deserialise($infile);
213             }
214             }
215             else {
216 0           croak 'Parsing failed.';
217             }
218             }
219            
220             }
221 0           my @existingTests = () ;
222 0 0         if (defined $testSet) {
223 0           @existingTests = map {$_->testName()} @{$testSet->tests()};
  0            
  0            
224             }
225            
226            
227 0           my $database = databaseName($dbh);
228            
229 1     1   4 no warnings;
  1         1  
  1         79  
230 0 0         my $userName = $OSNAME eq 'MSWin32' ? Win32::LoginName : ${[getpwuid( $< )]}->[6]; $userName =~ s/,.*//;
  0            
  0            
231 1     1   4 use warnings;
  1         1  
  1         546  
232 0           my $date = strftime "%d/%m/%Y", localtime;
233             #warn Dumper $userName ;
234             #warn Dumper $ra_columns ;
235             #exit ;
236            
237 0           my $execs = ExecSp($dbh) ;
238            
239             #warn Dumper $widest_column_name_padding;
240            
241 0           foreach my $exec (@$execs) {
242            
243 0           my $ofile = $$exec[0];
244            
245 0           (my $fileName = "${ofile}" ) =~ s{[.]}{_} ;
246 0           $fileName =~ s{[\]\[]}{}g ;
247 0           $fileName =~ s{\s}{}g ;
248 0           my $testName = $fileName;
249            
250             # if not already defined in the test file (if given)
251 0 0   0     if ( (firstidx { $_ eq $testName } @existingTests ) == -1 ) {
  0            
252            
253            
254 0           my $checkText = "";
255 0           my $receivingTable = "" ;
256            
257 0 0         if ( $runChecks ) {
258            
259 0           $checkText = CheckForExceptions($dbh, $dbh_typeinfo, $$exec[0], $userName, $date, $$exec[1],$$exec[2] ) ;
260            
261 0           my $resultsTable = undef ;
262 0 0 0       if ( ! defined $checkText || $checkText eq q() ) {
263 0           $resultsTable = CheckForResults($dbh, $dbh_typeinfo, $$exec[0], $userName, $date, $$exec[1],$$exec[2] ) ;
264             }
265             #warn Dumper "--------------------------";
266             #warn Dumper $resultsTable;
267             #warn Dumper scalar @$resultsTable ;
268             #warn Dumper @{$resultsTable->[0]};
269 0 0 0       if (defined $resultsTable && scalar @$resultsTable eq 1 && scalar @{$resultsTable->[0]} gt 0 ) {
  0   0        
270 0           $receivingTable = do { local $"= "\n\t,\t\t" ; "\tdeclare \@ResultSet table\n\t(\t\t@{$resultsTable->[0]} \n\t)" } ;
  0            
  0            
  0            
271             # $receivingTable = do { local $"= "\n\t,\t\t" ; "@{$resultsTable->[0]}" } ;
272             }
273             #elsif (scalar @$resultsTable gt 1 ) {
274             # $receivingTable = "More than one set of results - can't capture them" } ;
275             #}
276             #warn Dumper $receivingTable ;
277             #warn Dumper $$exec[2];
278             } ;
279            
280 0           my $text = Template($dbh, $dbh_typeinfo, $$exec[0], $userName, $date, $$exec[1],$$exec[2],$checkText,$receivingTable ) ;
281 0           $fileName .= ".sql";
282            
283 0           my $fh = IO::File->new("> ${dirs}/${fileName}") ;
284            
285 0 0         if (defined ${fh} ) {
286 0           print {${fh}} $text ;
  0            
287 0           $fh->close;
288             }
289             else {
290 0           croak "Unable to write to ${ofile}.sql.";
291             }
292             }
293             }
294            
295 0           exit;
296             }
297            
298             sub Template {
299            
300             local $_ = undef;
301            
302             my $dbh = shift ;
303             my $dbh_typeinfo = shift ;
304            
305             my $sut = shift ;
306            
307             my $userName = shift ;
308             my $date = shift ;
309            
310             my $declaration = shift ;
311             my $code = shift ;
312            
313             my $checkText = shift ;
314             my $receivingTable = shift ;
315            
316             if (defined $checkText) {
317             $checkText = "\t--\t Raises this error:- " . $checkText ;
318             }
319             else {
320             $checkText = q();
321             }
322             if ($receivingTable ne '') {
323             $code = "insert into \@ResultSet\n\t" . $code ;
324             }
325            
326            
327             return <<"EOF";
328            
329            
330             /* AUTHOR
331             * ${userName}
332             *
333             * DESCRIPTION
334             * Tests the minimal case for ${sut}
335             * Runs a basic smoke-test.
336             *
337             * SUT
338             * ${sut}
339             *
340             * OTHER
341             * Other notes.
342             *
343             * CHANGE HISTORY
344             * ${date} ${userName}
345             * Created.
346             */
347            
348            
349             set nocount on
350            
351             begin try
352            
353             declare \@testStatus varchar(100)
354             set \@testStatus = 'Passed'
355            
356             begin transaction
357            
358             ${checkText}
359            
360             ${declaration}
361            
362             ${receivingTable}
363            
364             ${code}
365            
366             select \@testStatus
367            
368            
369             end try
370             begin catch
371            
372             set \@testStatus = 'Failed'
373            
374             select \@testStatus
375             select error_state()
376             select error_message()
377             select error_number()
378            
379             end catch
380            
381            
382             if \@\@trancount > 0 or xact_state() = -1
383             rollback
384            
385            
386             EOF
387            
388             }
389            
390            
391             sub CheckForExceptions {
392            
393             local $_ = undef;
394            
395             my $dbh = shift ;
396             my $dbh_typeinfo = shift ;
397            
398             my $sut = shift ;
399            
400             my $userName = shift ;
401             my $date = shift ;
402            
403             my $declaration = shift ;
404             my $code = shift ;
405            
406             my $sql = CheckForExceptionsSQL($declaration,$code) ;
407            
408             my @run1_res ;
409             my @res_col ;
410             my @res_type ;
411             my $sth = $dbh->prepare($sql,{odbc_exec_direct => 1});
412            
413             try {
414             $sth->execute;
415            
416             do {
417             push @res_type, $sth->{TYPE} ;
418             push @res_col, $sth->{NAME} ;
419            
420             no warnings;
421             push @run1_res, $sth->fetchall_arrayref() ;
422             use warnings;
423             } while ($sth->{odbc_more_results}) ;
424             } catch {
425             warn "SUT :- $sut\n";
426             };
427             #warn Dumper @run1_res ;
428             my $err = undef;
429             if ( scalar @run1_res && scalar @{$run1_res[0]} && $run1_res[0][0][0] eq 'VSGDR::TestScriptGen - raised exception') {
430             $err = $run1_res[0][0][1];
431             }
432             #warn Dumper $err ;
433             return $err;
434             }
435            
436             sub CheckForExceptionsSQL {
437            
438             local $_ = undef;
439            
440             my $declaration = shift ;
441             my $code = shift ;
442            
443             return <<"EOF";
444            
445            
446             set nocount on
447            
448             begin try
449            
450             begin transaction
451            
452             ${declaration}
453             ${code}
454            
455             end try
456             begin catch
457            
458             select 'VSGDR::TestScriptGen - raised exception', error_message()
459            
460             end catch
461            
462            
463             if \@\@trancount > 0 or xact_state() = -1
464             rollback
465            
466            
467             EOF
468            
469             }
470            
471             sub CheckForResults {
472            
473             local $_ = undef;
474            
475             my $dbh = shift ;
476             my $dbh_typeinfo = shift ;
477            
478             my $sut = shift ;
479            
480             my $userName = shift ;
481             my $date = shift ;
482            
483             my $declaration = shift ;
484             my $code = shift ;
485            
486             my $sql = CheckForResultsSQL($declaration,$code) ;
487            
488             my @run1_res ;
489             my @res_col ;
490             my @res_type ;
491             my $sth = $dbh->prepare($sql,{odbc_exec_direct => 1});
492            
493             try {
494             $sth->execute;
495            
496             do {
497             push @res_type, $sth->{TYPE} ;
498             push @res_col, $sth->{NAME} ;
499            
500             my @names = map { scalar $dbh_typeinfo->type_info($_)->{TYPE_NAME} } @{ $sth->{TYPE} } ;
501             my @colSize = map { scalar $dbh_typeinfo->type_info($_)->{COLUMN_SIZE} } @{ $sth->{TYPE} } ;
502            
503             my @types = () ;
504             my @spec = () ;
505             #warn Dumper $sth->{TYPE} ;
506             #warn Dumper $sth->{NUM_OF_FIELDS} ;
507             if (scalar @names) {
508             my $col=1;
509             @types = List::MoreUtils::pairwise { $a =~ m{char|binary}ism ? "$a($b)" : "$a" } @names, @colSize ;
510             @spec = List::MoreUtils::pairwise { ( ($a eq "" ) ? "[Column_" . ${col}++ . "]" : "[$a]" ) . "\t\t\t$b" } @{$sth->{NAME}}, @types ;
511             }
512            
513             #warn Dumper @spec;
514            
515             #do { local $"= "\n,\t" ;
516             # say {*STDERR} "ResultSet(\n\t@{spec}\n)";
517             # };
518            
519             no warnings;
520             push @run1_res, \@spec ;
521             use warnings;
522            
523            
524             } while ($sth->{odbc_more_results}) ;
525             } catch {
526             warn "SUT :- $sut\n";
527             };
528            
529             return \@run1_res;
530             }
531            
532            
533             sub CheckForResultsSQL {
534            
535             local $_ = undef;
536            
537             my $declaration = shift ;
538             my $code = shift ;
539            
540             return <<"EOF";
541            
542            
543             set nocount on
544            
545             begin try
546            
547             begin transaction
548            
549             ${declaration}
550             ${code}
551            
552             end try
553             begin catch
554            
555             select 'VSGDR::TestScriptGen - raised exception', error_message()
556            
557             end catch
558            
559            
560             if \@\@trancount > 0 or xact_state() = -1
561             rollback
562            
563            
564             EOF
565            
566             }
567            
568            
569            
570             1;
571            
572             __DATA__