~ubuntu-branches/ubuntu/saucy/drizzle/saucy-proposed

« back to all changes in this revision

Viewing changes to tests/lib/dbqp_modes/dtr/dtr_test_management.py

  • Committer: Package Import Robot
  • Author(s): Clint Byrum
  • Date: 2012-06-19 10:46:49 UTC
  • mfrom: (1.1.6)
  • mto: This revision was merged to the branch mainline in revision 29.
  • Revision ID: package-import@ubuntu.com-20120619104649-e2l0ggd4oz3um0f4
Tags: upstream-7.1.36-stable
ImportĀ upstreamĀ versionĀ 7.1.36-stable

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#! /usr/bin/env python
 
2
# -*- mode: python; indent-tabs-mode: nil; -*-
 
3
# vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
 
4
#
 
5
# Copyright (C) 2010 Patrick Crews
 
6
#
 
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
 
20
 
 
21
""" dtr_test_management:
 
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
from ConfigParser import RawConfigParser
 
35
 
 
36
import lib.test_mgmt.test_management as test_management
 
37
 
 
38
 
 
39
    
 
40
class testCase:
 
41
    """Holds info on a per .test file basis
 
42
       Attributes contain information necessary to execute / validate
 
43
       the test file when it is executed.
 
44
 
 
45
    """
 
46
    def __init__(self, system_manager, test_case=None, test_name=None, suite_name=None
 
47
                 , suite_path=None, test_server_options=[], test_path=None, result_path=None
 
48
                 , comment=None, master_sh=None, cnf_path=None
 
49
                 , disable=0, innodb_test=1 
 
50
                 , need_debug=0, debug=0):
 
51
        self.system_manager = system_manager
 
52
        self.logging = self.system_manager.logging
 
53
        self.skip_keys = ['system_manager']
 
54
        self.testcase = test_case
 
55
        self.testname = test_name
 
56
        self.suitename = suite_name
 
57
        self.suitepath = suite_path
 
58
        self.fullname = "%s.%s" %(suite_name, test_name)
 
59
        self.testpath = test_path
 
60
        self.resultpath = result_path
 
61
 
 
62
        self.skip_flag = 0
 
63
        self.timezone = "GMT-3"
 
64
        self.component_id = "drizzled"
 
65
        self.slave_count = 0
 
66
        self.master_count = 1
 
67
        self.server_options = test_server_options
 
68
        # We will populate this in a better fashion later on
 
69
        # as we allow .cnf files, we need to do a bit of
 
70
        # messing about to make this work right
 
71
        if self.server_options == [] or type(self.server_options[0]) is not list:
 
72
            self.server_requirements=[self.server_options]
 
73
        else:
 
74
            self.server_requirements = self.server_options
 
75
            self.server_options= self.server_options[0][0]
 
76
        self.comment = comment
 
77
        self.master_sh = master_sh
 
78
        self.cnf_path = cnf_path
 
79
        self.disable = disable
 
80
        self.innodb_test  = innodb_test
 
81
        self.need_debug = need_debug
 
82
        
 
83
        self.system_manager.logging.debug_class(self)
 
84
 
 
85
    def should_run(self):
 
86
        if self.skip_flag or self.disable:
 
87
            return 0
 
88
        else:
 
89
            return 1
 
90
 
 
91
 
 
92
        
 
93
        
 
94
          
 
95
class testManager(test_management.testManager):
 
96
    """Deals with scanning test directories, gathering test cases, and 
 
97
       collecting per-test information (opt files, etc) for use by the
 
98
       test-runner
 
99
 
 
100
    """
 
101
 
 
102
    def process_suite(self,suite_dir):
 
103
        """Process a test suite.
 
104
           This includes searching for tests in test_list and only
 
105
           working with the named tests (all tests in suite is the default)
 
106
           Further processing includes reading the disabled.def file
 
107
           to know which tests to skip, processing the suite.opt file,
 
108
           and processing the individual test cases for data relevant
 
109
           to the rest of the test-runner
 
110
        
 
111
        """
 
112
 
 
113
        self.system_manager.logging.verbose("Processing suite: %s" %(suite_dir))
 
114
 
 
115
        # Generate our test and result files:
 
116
        testdir = os.path.join(suite_dir, 't')
 
117
        resultdir = os.path.join(suite_dir, 'r')
 
118
 
 
119
        # Do basic checks to make sure this is worth further work
 
120
        self.check_suite(suite_dir, testdir, resultdir)      
 
121
 
 
122
        # Get suite-level options
 
123
        suite_options = []
 
124
        cnf_path, suite_options = self.process_suite_options(suite_dir) 
 
125
 
 
126
        # Get the 'name' of the suite.  This can require some processing
 
127
        # But the name is useful for reporting and whatnot
 
128
        suite_name = self.get_suite_name(suite_dir)
 
129
        
 
130
        # Get the contents of the testdir and filter it accordingly
 
131
        # This applies do-test / skip-test filters and any specific
 
132
        # test case names        
 
133
        testlist = self.testlist_filter(os.listdir(testdir))
 
134
        # sort our list if no individual tests were specified
 
135
        if not self.desired_tests:
 
136
            testlist.sort()
 
137
                       
 
138
        # gather deeper information on tests we are interested in
 
139
        if testlist:
 
140
            # We have tests we want to process, we gather additional information
 
141
            # that is useful at the suite level.  This includes disabled tests          
 
142
            # Gather disabled test list.  
 
143
            # This is used in process_test_file()
 
144
            disabled_tests = {}
 
145
            disabled_tests = self.process_disabled_test_file(testdir)                        
 
146
            for test_case in testlist:
 
147
                self.add_test(self.process_test_file(suite_dir,
 
148
                                              suite_name, cnf_path, suite_options
 
149
                                              , disabled_tests, testdir 
 
150
                                              , resultdir, test_case))
 
151
 
 
152
    def process_test_file(self, suite_dir, suite_name, suite_cnf_path, suite_options 
 
153
                          , disabled_tests, testdir 
 
154
                          , resultdir, test_case):
 
155
        """ We generate / find / store all the relevant information per-test.
 
156
            This information will be used when actually executing the test
 
157
            We store the data in a testCase object 
 
158
 
 
159
        """
 
160
        
 
161
        test_server_options = suite_options
 
162
        test_name = test_case.replace('.test','')
 
163
        self.system_manager.logging.verbose("Processing test: %s.%s" %(suite_name,test_name))
 
164
 
 
165
        
 
166
        # Fix this , create a testCase with gather_test_data() passed
 
167
        # as the argument
 
168
        # Ensure we pass everything we need and use it all
 
169
        (  test_path
 
170
         , result_file_name
 
171
         , result_path
 
172
         , comment
 
173
         , master_sh
 
174
         , cnf_path
 
175
         , test_server_options
 
176
         , disable
 
177
         , innodb_test
 
178
         , need_debug) = self.gather_test_data(test_case, test_name,
 
179
                                 suite_name, test_server_options,testdir, 
 
180
                                 resultdir, disabled_tests)
 
181
        if suite_cnf_path and not cnf_path:
 
182
            cnf_path=suite_cnf_path
 
183
        test_case = testCase(self.system_manager, test_case, test_name, suite_name, 
 
184
                             suite_dir, test_server_options,test_path, result_path,
 
185
                             master_sh=master_sh, cnf_path=cnf_path, debug=self.debug)      
 
186
        return test_case
 
187
 
 
188
 
 
189
########################################################################
 
190
# utility functions
 
191
#
 
192
# Stuff that helps us out and simplifies our main functions
 
193
# But isn't that important unless you need to dig deep
 
194
########################################################################
 
195
 
 
196
    def gather_test_data(self, test_case, test_name, suite_name,
 
197
                         test_server_options, testdir, resultdir, disabled_tests):
 
198
        """ We gather all of the data needed to produce a testCase for
 
199
            a given test
 
200
 
 
201
        """
 
202
 
 
203
        test_path = os.path.join(testdir,test_case)
 
204
        result_file_name = test_name+'.result'       
 
205
        result_path = self.find_result_path(resultdir, result_file_name)
 
206
        comment = None
 
207
        master_sh_path = test_path.replace('.test','-master.sh')
 
208
        if os.path.exists(master_sh_path):
 
209
            master_sh = master_sh_path 
 
210
        else:
 
211
            master_sh = None
 
212
        master_opt_path = test_path.replace('.test', '-master.opt')
 
213
        config_file_path = test_path.replace('.test', '.cnf')
 
214
        # NOTE:  this currently makes suite level server options additive 
 
215
        # to file-level .opt files...not sure if this is the best.
 
216
        test_server_options = test_server_options + self.process_opt_file(
 
217
                                                           master_opt_path)
 
218
        # deal with .cnf files (which supercede master.opt stuff)
 
219
        cnf_options = []
 
220
        cnf_flag, returned_options = self.process_cnf_file(config_file_path)
 
221
        cnf_options += returned_options
 
222
        if cnf_flag: # we found a proper file and need to override
 
223
            found_options = cnf_options
 
224
        else:
 
225
            config_file_path = None
 
226
        (disable, comment) = self.check_if_disabled(disabled_tests, test_name)
 
227
        innodb_test = 0
 
228
        need_debug = 0
 
229
        return (test_path, result_file_name, result_path, comment, master_sh, 
 
230
                config_file_path, test_server_options, disable, innodb_test, need_debug)
 
231
 
 
232
    def check_suite(self, suite_dir, testdir, resultdir):
 
233
        """Handle basic checks of the suite:
 
234
           does the suite exist?
 
235
           is there a /t dir?
 
236
           is there a /r dir?
 
237
           Error and die if not
 
238
 
 
239
        """
 
240
        # We expect suite to be a path name, no fuzzy searching
 
241
        if not os.path.exists(suite_dir):
 
242
            self.system_manager.logging.error("Suite: %s does not exist" %(suite_dir))
 
243
            sys.exit(1)
 
244
                
 
245
        # Ensure our test and result directories are present
 
246
        if not os.path.exists(testdir):
 
247
            self.system_manager.logging.error("Suite: %s does not have a 't' directory (expected location for test files)" %(suite_dir))
 
248
            sys.exit(1)
 
249
        if not os.path.exists(resultdir):
 
250
            self.system_manager.logging.error("Suite: %s does not have an 'r' directory (expected location for result files)" %(suite_dir))
 
251
            sys.exit(1)
 
252
 
 
253
    def get_suite_name(self, suite_dir):
 
254
        """ Get the 'name' of the suite
 
255
            This can either be the path basename or one directory up, as
 
256
            in the case of files in the drizzle/plugins directory
 
257
        
 
258
        """
 
259
 
 
260
        # We trim any trailing path delimiters as python returns
 
261
        # '' for basedir if the path ends that way
 
262
        # BEGIN horrible hack to accomodate bad location of main suite : /
 
263
        if suite_dir == self.testdir:
 
264
            return 'main'
 
265
        # END horrible hack : /
 
266
        if suite_dir.endswith('/'):
 
267
            suite_dir=suite_dir[:-1]
 
268
        suite_dir_root,suite_dir_basename = os.path.split(suite_dir)
 
269
        if suite_dir_basename == 'tests' or suite_dir_basename == 'drizzle-tests':
 
270
            suite_name = os.path.basename(suite_dir_root)
 
271
        else:
 
272
            suite_name = suite_dir_basename
 
273
        self.system_manager.logging.debug("Suite_name:  %s" %(suite_name))
 
274
        return suite_name
 
275
 
 
276
    def process_suite_options(self, suite_dir):
 
277
        """ Process the suite.opt and master.opt files
 
278
            that reside at the suite-level if they exist.
 
279
            Return a list of the options found
 
280
 
 
281
            We also process .cnf files - this
 
282
            is currently dbqp-only and is the proper
 
283
            way to do things :P
 
284
 
 
285
        """
 
286
        found_options = []
 
287
        opt_files = ['t/master.opt','t/suite.opt']
 
288
        for opt_file in opt_files:
 
289
            found_options = found_options + self.process_opt_file(os.path.join(suite_dir,opt_file))
 
290
        # We also process the suite-level .cnf file(s).  We override
 
291
        # a master.opt file if we have a .cnf file.  There is no reason they
 
292
        # should ever be used in conjunction and I am biased towards .cnf ; )
 
293
        cnf_files = ['t/master.cnf']
 
294
        cnf_options = []
 
295
        for cnf_file in cnf_files:
 
296
            config_file_path = os.path.join(suite_dir,cnf_file)
 
297
            cnf_flag, returned_options = self.process_cnf_file(config_file_path)
 
298
            cnf_options += returned_options
 
299
        if cnf_flag: # we found a proper file and need to override
 
300
            found_options = cnf_options
 
301
        else:
 
302
            config_file_path = None
 
303
        return config_file_path, found_options
 
304
 
 
305
    def process_disabled_test_file(self, testdir):
 
306
        """ Checks and processes the suite's disabled.def
 
307
            file.  This file must reside in the suite/t directory
 
308
            It must be in the format:
 
309
            test-name : comment (eg BugNNNN - bug on hold, test disabled)
 
310
            In reality a test should *never* be disabled.  EVER.
 
311
            However, we keep this as a bit of utility
 
312
 
 
313
        """
 
314
        disabled_tests = {}
 
315
        disabled_def_path = os.path.join(testdir,'disabled.def')
 
316
        if not os.path.exists(disabled_def_path):
 
317
            return disabled_tests
 
318
 
 
319
        try:
 
320
            disabled_test_file = open(disabled_def_path,'r')
 
321
        except IOError, e: 
 
322
            self.system_manager.logging.error("Problem opening disabled.def file: %s" %(disabled_def_path))
 
323
            sys.exit(1)
 
324
 
 
325
        self.system_manager.logging.debug("Processing disabled.def file: %s" %(disabled_def_path))
 
326
        disabled_bug_pattern = re.compile("[\S]+[\s]+:[\s]+[\S]")
 
327
        
 
328
        for line in disabled_test_file:
 
329
            line = line.strip()
 
330
            if not line.startswith('#'): # comment
 
331
                if re.match(disabled_test_pattern,line):
 
332
                    self.system_manager.logging.debug("found disabled test - %s" %(line))
 
333
                    test_name, test_comment = line.split(':')
 
334
                    disabled_tests[test_name.strip()]=test_comment.strip() 
 
335
            
 
336
        disabled_test_file.close()
 
337
        return disabled_tests
 
338
        
 
339
 
 
340
    def process_opt_file(self, opt_file_path):
 
341
        """ Process a test-run '.opt' file.
 
342
        These files contain test and suite-specific server options
 
343
        (ie what options the server needs to use for the test) 
 
344
 
 
345
        Returns a list of the options (we don't really validate...yet)
 
346
         
 
347
        NOTE:  test-run.pl allows for server *and* system options
 
348
        (eg timezone, slave_count, etc) in opt files.  We don't.
 
349
        None of our tests use this and we should probably avoid it
 
350
        We can introduce server and system .opt files or even better
 
351
        would be to use config files as we do with drizzle-automation
 
352
        This would allow us to specify options for several different
 
353
        things in a single file, but in a clean and standardized manner
 
354
 
 
355
        """
 
356
        found_options = []
 
357
        if not os.path.exists(opt_file_path):
 
358
            return found_options
 
359
 
 
360
        try:
 
361
            opt_file = open(opt_file_path,'r')
 
362
        except IOError, e: 
 
363
            self.system_manager.logging.error("Problem opening option file: %s" %(opt_file_path))
 
364
            sys.exit(1)
 
365
 
 
366
        self.system_manager.logging.debug("Processing opt file: %s" %(opt_file_path))
 
367
        for line in opt_file:
 
368
            options = line.split('--')
 
369
            if options:
 
370
                for option in options:
 
371
                    if option:
 
372
                        if 'restart' in option or '#' in option:
 
373
                            option = 'restart'
 
374
                        found_options.append('--%s' %(option.strip()))
 
375
        opt_file.close()
 
376
        return found_options
 
377
 
 
378
    def process_cnf_file(self, cnf_file_path):
 
379
        """ We extract meaningful information from a .cnf file
 
380
            if it exists.  Currently limited to server allocation
 
381
            needs
 
382
 
 
383
        """
 
384
 
 
385
        server_requirements = []
 
386
        cnf_flag = 0
 
387
        if os.path.exists(cnf_file_path):
 
388
            cnf_flag = 1
 
389
            config_reader = RawConfigParser()
 
390
            config_reader.read(cnf_file_path)
 
391
            server_requirements = self.process_server_reqs(config_reader.get('test_servers','servers'))
 
392
        return ( cnf_flag, server_requirements )
 
393
 
 
394
    def process_server_reqs(self,data_string):
 
395
        """ We read in the list of lists as a string, so we need to 
 
396
            handle this / break it down into proper chunks
 
397
 
 
398
        """
 
399
        server_reqs = []
 
400
        # We expect to see a list of lists and throw away the 
 
401
        # enclosing brackets
 
402
        option_sets = data_string[1:-1].strip().split(',')
 
403
        for option_set in option_sets:
 
404
            server_reqs.append([option_set[1:-1].strip()])
 
405
        return server_reqs
 
406
 
 
407
    def testlist_filter(self, testlist):
 
408
        """ Filter our list of testdir contents based on several 
 
409
            criteria.  This looks for user-specified test-cases
 
410
            and applies the do-test and skip-test filters
 
411
 
 
412
            Returns the list of tests that we want to execute
 
413
            for further processing
 
414
 
 
415
        """
 
416
 
 
417
        # We want only .test files
 
418
        # Possible TODO:  allow alternate test extensions
 
419
        testlist = [test_file for test_file in testlist if test_file.endswith('.test')]
 
420
         
 
421
        # Search for specific test names
 
422
        if self.desired_tests: # We have specific, named tests we want from the suite(s)
 
423
           tests_to_use = []
 
424
           for test in self.desired_tests:
 
425
               if test.endswith('.test'): 
 
426
                   pass
 
427
               else:
 
428
                   test = test+'.test'
 
429
               if test in testlist:
 
430
                   tests_to_use.append(test)
 
431
           testlist = tests_to_use
 
432
 
 
433
        # TODO:  Allow for regex?
 
434
        # Apply do-test filter
 
435
        if self.dotest:
 
436
            testlist = [test_file for test_file in testlist if test_file.startswith(self.dotest)]
 
437
        # Apply skip-test filter
 
438
        if self.skiptest:
 
439
            testlist = [test_file for test_file in testlist if not test_file.startswith(self.skiptest)]
 
440
        return testlist
 
441
 
 
442
    def find_result_path(self, result_dir, result_file_name):
 
443
        """ This is copied from test-run.pl dtr_cases.pl
 
444
            If we have an engine option passed in and the 
 
445
            path resultdir/engine/testname.result exists, that is 
 
446
            our .result file
 
447
        
 
448
            Need to check if we really need this - maybe PBXT?
 
449
 
 
450
        """
 
451
        result_path = os.path.join(result_dir,result_file_name)
 
452
        if self.default_engine:
 
453
            candidate_path = os.path.join(result_dir, self.default_engine, 
 
454
                                          result_file_name)
 
455
            if os.path.exists(candidate_path):
 
456
                result_path = candidate_path
 
457
        return result_path
 
458
 
 
459
    def check_if_disabled(self, disabled_tests, test_name):
 
460
        """ Scan the list of disabled tests if it exists to see 
 
461
            if the test is disabled.
 
462
        
 
463
        """
 
464
   
 
465
        if disabled_tests:
 
466
            if test_name in disabled_tests:
 
467
                self.system_manager.logging.debug("%s says - I'm disabled" %(test_name))
 
468
                return (1, disabled_tests[test_name])
 
469
        return (0,None)
 
470
 
 
471
    def sort_testcases(self):
 
472
        """ We sort our testcases according to the server_options they have
 
473
            For each testcase, we sort the list of options, so if a test has
 
474
            --plugin-add=csv --abracadabra, we would get 
 
475
            --abracadabra --plugin-add=csv
 
476
            
 
477
            This results in tests that have similar options being run in order
 
478
            this minimizes server restarts which can be costly
 
479
 
 
480
        """
 
481
        test_management.testManager.sort_testcases(self)
 
482
        organizer = {}
 
483
        ordered_list = []
 
484
        for testcase in self.test_list:
 
485
            key = " ".join(sorted(testcase.server_options))
 
486
            if key in organizer:
 
487
                organizer[key].append(testcase)
 
488
            else:
 
489
                organizer[key] = [testcase]
 
490
        for value_list in organizer.values():
 
491
            ordered_list = ordered_list + value_list
 
492
        self.test_list = ordered_list
 
493
        
 
494
 
 
495