~drizzle-trunk/drizzle/development

2144.1.1 by patrick crews
Overhaul of code. We can run rabbitmq : ) We now better encapsulate a per-executor working environment = one step closer to --parallel >: ) using subprocess goodness for server control
1
#! /usr/bin/env python
2235.5.2 by Stewart Smith
dbqp source (and all its libs) should use emacs python mode, not emacs C mode
2
# -*- mode: python; indent-tabs-mode: nil; -*-
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
3
# vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
4
#
5
# Copyright (C) 2010 Patrick Crews
6
#
2121.3.2 by patrick crews
Updated license verbiage
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
11
#
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
2121.3.1 by patrick crews
Added licensing text to dbqp files
20
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
21
""" test_management:
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
22
    code related to the gathering / analysis / management of 
23
    the test cases
24
    ie - collecting the list of tests in each suite, then
25
    gathering additional, relevant information for the test-runner's dtr
26
    mode. (traditional diff-based testing)
27
28
"""
29
30
# imports
31
import os
32
import re
33
import sys
34
import thread
35
          
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
36
class testManager(object):
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
37
    """Deals with scanning test directories, gathering test cases, and 
38
       collecting per-test information (opt files, etc) for use by the
39
       test-runner
40
41
    """
42
2337.1.1 by patrick crews
Cleanup of option handling + test modes. Initial work for expanding dbqp capabilities to do neat things
43
    def __init__( self, variables, system_manager):
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
44
45
        self.system_manager = system_manager
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
46
        self.time_manager = system_manager.time_manager
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
47
        self.logging = system_manager.logging
2337.1.1 by patrick crews
Cleanup of option handling + test modes. Initial work for expanding dbqp capabilities to do neat things
48
        if variables['verbose']:
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
49
            self.logging.verbose("Initializing test manager...")
50
51
        self.skip_keys = [ 'system_manager'
2337.1.1 by patrick crews
Cleanup of option handling + test modes. Initial work for expanding dbqp capabilities to do neat things
52
                         , 'verbose'
53
                         , 'debug'
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
54
                         ]
55
        self.test_list = []
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
56
        self.first_test = 1
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
57
        self.total_test_count = 0
58
        self.executed_tests = {} # We have a hash of 'status':[test_name..]
59
        self.executing_tests = {}
2337.1.1 by patrick crews
Cleanup of option handling + test modes. Initial work for expanding dbqp capabilities to do neat things
60
        self.verbose = variables['verbose']
61
        self.debug = variables['debug']
62
        self.default_engine = variables['defaultengine']
63
        self.dotest = variables['dotest']
64
        if self.dotest:
65
            self.dotest = self.dotest.strip()
66
        self.skiptest = variables['skiptest']
67
        if self.skiptest:
68
            self.skiptest = self.skiptest.strip()
69
        self.reorder = variables['reorder']
70
        self.suitelist = variables['suitelist']
71
        self.mode = variables['mode']
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
72
        
2337.1.12 by patrick crews
Additional work on allowing dbqp to handle multiple server types / versions (even simultaneously)
73
        self.suitepaths = variables['suitepaths']
74
        self.testdir = variables['testdir']
2337.1.1 by patrick crews
Cleanup of option handling + test modes. Initial work for expanding dbqp capabilities to do neat things
75
        self.desired_tests = variables['test_cases']
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
76
        
2337.1.4 by patrick crews
Tweak of logging behavior (no more 'if debug' checks...) and related code cleanup. Also groundwork for allowing multiple basedirs (server code bases) to the test runner)
77
        self.logging.debug_class(self)
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
78
79
    def add_test(self, new_test_case):
80
        """ Add a new testCase to our self.test_list """
2337.1.4 by patrick crews
Tweak of logging behavior (no more 'if debug' checks...) and related code cleanup. Also groundwork for allowing multiple basedirs (server code bases) to the test runner)
81
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
82
        self.test_list.append(new_test_case)
83
        
84
    def gather_tests(self):
85
        self.logging.info("Processing test suites...")
86
        # BEGIN terrible hack to accomodate the fact that
87
        # our 'main' suite is also our testdir : /
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
88
        if self.suitelist is None and self.mode=='dtr':
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
89
            self.suitepaths = [self.testdir]
90
            self.suitelist = ['main']
91
        # END horrible hack
92
        for suite in self.suitelist:
93
            suite_path = self.find_suite_path(suite)
94
            if suite_path:
95
                self.process_suite(suite_path)
96
            else:
97
                self.logging.error("Could not find suite: %s in any of paths: %s" %(suite, ", ".join(self.suitepaths)))
98
        self.process_gathered_tests()
99
100
    def process_gathered_tests(self):
101
        """ We do some post-gathering analysis and whatnot
102
            Report an error if there were desired_tests but no tests
103
            were found.  Otherwise just report what we found
104
    
105
        """
106
2121.6.1 by patrick crews
Updates to allow for reorder option. Also added placeholder for --fast (it is one in test-run.pl as well), and a minor fix of output regarding libtool
107
        # See if we need to reorder our test cases
108
        if self.reorder:
109
            self.sort_testcases()
110
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
111
        if self.desired_tests and not self.test_list:
112
            # We wanted tests, but found none
113
            # Probably need to make this smarter at some point
114
            # To maybe make sure that we found all of the desired tests...
115
            # However, this is a start / placeholder code
116
            self.logging.error("Unable to locate any of the desired tests: %s" %(" ,".join(self.desired_tests)))   
117
        self.total_test_count = len(self.test_list)     
118
        self.logging.info("Found %d test(s) for execution" %(self.total_test_count))
119
        
2337.1.4 by patrick crews
Tweak of logging behavior (no more 'if debug' checks...) and related code cleanup. Also groundwork for allowing multiple basedirs (server code bases) to the test runner)
120
        self.logging.debug("Found tests:")
121
        self.logging.debug("%s" %(self.print_test_list()))
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
122
123
    def find_suite_path(self, suitename):
124
        """ We have a suitename, we need to locate the path to
125
            the juicy suitedir in one of our suitepaths.
126
127
            Theoretically, we could have multiple matches, but
128
            such things should never be allowed, so we don't
129
            code for it.  We return the first match.
130
 
131
            testdir can either be suitepath/suitename or
132
            suitepath/suitename/tests.  We test and return the
133
            existing path.   Return None if no match found
134
135
        """
136
        # BEGIN horrible hack to accomodate bad location of main suite
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
137
        if self.mode == 'dtr':
138
            if self.suitepaths == [self.testdir] or suitename == 'main':
139
                # We treat this as the 'main' suite
140
                return self.testdir
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
141
        # END horrible hack
142
        for suitepath in self.suitepaths:
143
            suite_path = self.system_manager.find_path([ os.path.join(suitepath,suitename,'tests'),
144
                                     os.path.join(suitepath,suitename) ], required = 0 )
145
            if suite_path:
146
                return suite_path
147
        return suite_path
148
149
    def process_suite(self,suite_dir):
150
        """Process a test suite.
151
           This includes searching for tests in test_list and only
152
           working with the named tests (all tests in suite is the default)
153
           Further processing includes reading the disabled.def file
154
           to know which tests to skip, processing the suite.opt file,
155
           and processing the individual test cases for data relevant
156
           to the rest of the test-runner
157
        
158
        """
2337.1.5 by patrick crews
Additonal code cleanup
159
        self.logging.verbose("Processing suite: %s" %(suite))
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
160
161
    def has_tests(self):
162
        """Return 1 if we have tests in our testlist, 0 otherwise"""
163
         
164
        return len(self.test_list)
165
166
    def get_testCase(self, requester):
167
        """return a testCase """
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
168
        if self.first_test:
169
            # we start our timer
170
            self.time_manager.start('total_time','total_time')
171
            self.first_test = 0
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
172
        test_case = None
173
        if self.has_tests():
174
            test_case = self.test_list.pop(0)
175
            self.record_test_executor(requester, test_case.fullname)
176
        return test_case
177
178
    def record_test_executor(self, requester, test_name):
179
        """ We record the test case and executor name as this could be useful
180
            We don't *know* this is needed, but we can always change this 
181
            later
182
 
183
        """
184
185
        self.executing_tests[test_name] = requester
186
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
187
    def record_test_result(self, test_case, test_status, output, exec_time):
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
188
        """ Accept the results of an executed testCase for further
189
            processing.
190
 
191
        """
192
        if test_status not in self.executed_tests:
193
            self.executed_tests[test_status] = [test_case]
194
        else:
195
            self.executed_tests[test_status].append(test_case)
2317.1.1 by patrick crews
Updates to dbqp to add a sysbench mode. Created test suites to duplicate readonly / readwrite drizzle-automation tests. Still needs some work, but tests execute
196
        # report.  If the test failed, we print any additional
197
        # output returned by the test executor
198
        # We may want to report additional output at other times
199
        if test_status != 'pass':
200
            report_output = True
201
        else:
202
            report_output = False
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
203
        self.logging.test_report( test_case.fullname, test_status
2317.1.1 by patrick crews
Updates to dbqp to add a sysbench mode. Created test suites to duplicate readonly / readwrite drizzle-automation tests. Still needs some work, but tests execute
204
                                , exec_time, output, report_output)
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
205
206
207
    def print_test_list(self):
208
        test_names = []
209
        for test in self.test_list:
210
            test_names.append(test.fullname)
211
        return "[ %s ]" %(", ".join(test_names))
212
213
    def statistical_report(self):
214
        """ Report out various testing statistics:
215
            Failed/Passed %success
216
            list of failed test cases
217
          
218
        """
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
219
        # This is probably hacky, but I'll think of a better
220
        # location later.  When we are ready to see our
221
        # statistical report, we know to stop the total time timer
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
222
        if not self.first_test:
223
            total_exec_time = self.time_manager.stop('total_time')
224
            self.logging.write_thick_line()
225
            self.logging.info("Test execution complete in %d seconds" %(total_exec_time))
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
226
        self.logging.info("Summary report:")
227
        self.report_executed_tests()
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
228
        self.report_test_statuses()
2194.2.1 by patrick crews
Integrated randgen with dbqp. We now have mode=randgen and a set of randgen test suites (very basic now). Output = same as dtr : ) We also have mode=cleanup to kill any servers we have started. Docs updates too. Gendata utility allows us to populate test servers
229
        if not self.first_test:
230
            self.time_manager.summary_report()
2088.9.18 by patrick crews
Updates to allow for timing of test cases and reporting and whatnot
231
232
    def report_test_statuses(self):
233
        """ Method to report out various test statuses we
234
            care about
235
236
        """
237
        test_statuses = [ 'fail'
238
                        , 'timeout'
239
                        , 'skipped'
240
                        , 'disabled'
241
                        ]
242
        for test_status in test_statuses:
243
            self.report_tests_by_status(test_status)
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
244
        
245
    def get_executed_test_count(self):
246
        """ Return how many tests were executed """
247
        total_count = 0
248
        for test_list in self.executed_tests.values():
249
            total_count = total_count + len(test_list)
250
        return total_count
251
252
    def report_executed_tests(self):
253
        """ Report out tests by status """
254
        total_executed_count = self.get_executed_test_count()
2088.9.15 by patrick crews
Removed innodb_bug53756.test as it was using MTR2 functionality we don't have. Tweaks to error handling for certain cases (non-existent suite)
255
        if self.total_test_count:
256
            executed_ratio = (float(total_executed_count)/float(self.total_test_count))
257
            executed_percent = executed_ratio * 100
258
        else:
259
        # We prevent division by 0 if we didn't find any tests to execute
260
            executed_ratio = 0
261
            executed_percent = 0
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
262
        self.logging.info("Executed %s/%s test cases, %.2f percent" %( total_executed_count
263
                                                                     , self.total_test_count
264
                                                                     , executed_percent))
265
266
        for test_status in self.executed_tests.keys():
267
            status_count = self.get_count_by_status(test_status)
268
            test_percent = (float(status_count)/float(total_executed_count))*100
2088.9.8 by patrick crews
Modified test_manager summary report + updated server startup options. Main suite passes now
269
            self.logging.info("STATUS: %s, %d/%d test cases, %.2f percent executed" %( test_status.upper()
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
270
                                                                , status_count
271
                                                                , total_executed_count
272
                                                                , test_percent 
2088.9.8 by patrick crews
Modified test_manager summary report + updated server startup options. Main suite passes now
273
                                                                ))
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
274
275
2088.9.6 by patrick crews
Updates to reporting
276
    def report_tests_by_status(self, status):
277
        matching_tests = []
278
        if status in self.executed_tests:
279
            for testcase in self.executed_tests[status]:
280
                matching_tests.append(testcase.fullname)
281
            self.logging.info("%s tests: %s" %(status.upper(), ", ".join(matching_tests)))
2088.9.1 by patrick crews
Updated tree so that test-run.pl and test-run.py may live together in peace for a time
282
283
    def get_count_by_status(self, test_status):
284
        """ Return how many tests are in a given test_status """
285
        if test_status in self.executed_tests:
286
            return len(self.executed_tests[test_status])
287
        else:
288
            return 0
289
2121.6.1 by patrick crews
Updates to allow for reorder option. Also added placeholder for --fast (it is one in test-run.pl as well), and a minor fix of output regarding libtool
290
    def sort_testcases(self):
291
        """ Sort testcases to optimize test execution.
292
            This can be very mode-specific
293
294
        """
295
  
296
        self.logging.verbose("Reordering testcases to optimize test execution...")
297
2151.8.1 by patrick crews
Updates to allow several dbqp's run run on one system (via uuid) and fix to return a relevant code post-execution (ie 1 if tests failed or timed out))
298
    def has_failing_tests(self):
299
        return (self.get_count_by_status('fail') + self.get_count_by_status('timeout'))
300