2
# -*- mode: python; indent-tabs-mode: nil; -*-
3
# vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
5
# Copyright (C) 2010 Patrick Crews
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.
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.
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
21
""" dtr_test_management:
22
code related to the gathering / analysis / management of
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)
34
from ConfigParser import RawConfigParser
36
import lib.test_mgmt.test_management as test_management
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.
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
63
self.timezone = "GMT-3"
64
self.component_id = "drizzled"
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]
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
83
self.system_manager.logging.debug_class(self)
86
if self.skip_flag or self.disable:
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
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
113
self.system_manager.logging.verbose("Processing suite: %s" %(suite_dir))
115
# Generate our test and result files:
116
testdir = os.path.join(suite_dir, 't')
117
resultdir = os.path.join(suite_dir, 'r')
119
# Do basic checks to make sure this is worth further work
120
self.check_suite(suite_dir, testdir, resultdir)
122
# Get suite-level options
124
cnf_path, suite_options = self.process_suite_options(suite_dir)
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)
130
# Get the contents of the testdir and filter it accordingly
131
# This applies do-test / skip-test filters and any specific
133
testlist = self.testlist_filter(os.listdir(testdir))
134
# sort our list if no individual tests were specified
135
if not self.desired_tests:
138
# gather deeper information on tests we are interested in
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()
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))
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
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))
166
# Fix this , create a testCase with gather_test_data() passed
168
# Ensure we pass everything we need and use it all
175
, test_server_options
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)
189
########################################################################
192
# Stuff that helps us out and simplifies our main functions
193
# But isn't that important unless you need to dig deep
194
########################################################################
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
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)
207
master_sh_path = test_path.replace('.test','-master.sh')
208
if os.path.exists(master_sh_path):
209
master_sh = master_sh_path
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(
218
# deal with .cnf files (which supercede master.opt stuff)
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
225
config_file_path = None
226
(disable, comment) = self.check_if_disabled(disabled_tests, test_name)
229
return (test_path, result_file_name, result_path, comment, master_sh,
230
config_file_path, test_server_options, disable, innodb_test, need_debug)
232
def check_suite(self, suite_dir, testdir, resultdir):
233
"""Handle basic checks of the suite:
234
does the suite exist?
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))
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))
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))
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
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:
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)
272
suite_name = suite_dir_basename
273
self.system_manager.logging.debug("Suite_name: %s" %(suite_name))
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
281
We also process .cnf files - this
282
is currently dbqp-only and is the proper
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']
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
302
config_file_path = None
303
return config_file_path, found_options
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
315
disabled_def_path = os.path.join(testdir,'disabled.def')
316
if not os.path.exists(disabled_def_path):
317
return disabled_tests
320
disabled_test_file = open(disabled_def_path,'r')
322
self.system_manager.logging.error("Problem opening disabled.def file: %s" %(disabled_def_path))
325
self.system_manager.logging.debug("Processing disabled.def file: %s" %(disabled_def_path))
326
disabled_bug_pattern = re.compile("[\S]+[\s]+:[\s]+[\S]")
328
for line in disabled_test_file:
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()
336
disabled_test_file.close()
337
return disabled_tests
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)
345
Returns a list of the options (we don't really validate...yet)
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
357
if not os.path.exists(opt_file_path):
361
opt_file = open(opt_file_path,'r')
363
self.system_manager.logging.error("Problem opening option file: %s" %(opt_file_path))
366
self.system_manager.logging.debug("Processing opt file: %s" %(opt_file_path))
367
for line in opt_file:
368
options = line.split('--')
370
for option in options:
372
if 'restart' in option or '#' in option:
374
found_options.append('--%s' %(option.strip()))
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
385
server_requirements = []
387
if os.path.exists(cnf_file_path):
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 )
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
400
# We expect to see a list of lists and throw away the
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()])
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
412
Returns the list of tests that we want to execute
413
for further processing
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')]
421
# Search for specific test names
422
if self.desired_tests: # We have specific, named tests we want from the suite(s)
424
for test in self.desired_tests:
425
if test.endswith('.test'):
430
tests_to_use.append(test)
431
testlist = tests_to_use
433
# TODO: Allow for regex?
434
# Apply do-test filter
436
testlist = [test_file for test_file in testlist if test_file.startswith(self.dotest)]
437
# Apply skip-test filter
439
testlist = [test_file for test_file in testlist if not test_file.startswith(self.skiptest)]
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
448
Need to check if we really need this - maybe PBXT?
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,
455
if os.path.exists(candidate_path):
456
result_path = candidate_path
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.
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])
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
477
This results in tests that have similar options being run in order
478
this minimizes server restarts which can be costly
481
test_management.testManager.sort_testcases(self)
484
for testcase in self.test_list:
485
key = " ".join(sorted(testcase.server_options))
487
organizer[key].append(testcase)
489
organizer[key] = [testcase]
490
for value_list in organizer.values():
491
ordered_list = ordered_list + value_list
492
self.test_list = ordered_list