We wanted to have a little more flexibility with test_importer.py for chromeos. For example we wanted to import all tests from client/site_tests and server/site_tests but only some from client/tests and server/tests. We've added the following: -The ability to pass a whitelist with -w that specifies a subset of tests within a tests/profilers/samples directory. This option is incompatible with -a because if you're using -a you're probably already managing your tests by adding/deleting files. The whitelist attribute does not clean anything so the pattern will be to run test_importer.py with -C to clear all and then to run with -w. -The ability to adjust any test attributes as a site-specific extension. We will use the file site_set_attributes.py if it exists and it should include the function: 'def _set_attributes_custom(test, data)'. We use this to adjust the following: -Use the directory name instead of control-file NAME (handles multiple control files (control.xx) as dirname.xx. -Globally set run_verify to 0
Signed-off-by: Mike Truty <[email protected]> --- autotest/utils/test_importer.py 2010-03-25 11:20:10.000000000 -0700 +++ autotest/utils/test_importer.py 2010-03-25 11:20:10.000000000 -0700 @@ -30,78 +30,99 @@ import logging, re, os, sys, optparse, compiler from autotest_lib.frontend import setup_django_environment from autotest_lib.frontend.afe import models -from autotest_lib.client.common_lib import control_data +from autotest_lib.client.common_lib import control_data, utils -logging.basicConfig(level=logging.DEBUG) +logging.basicConfig(level=logging.ERROR) # Global DRY_RUN = False DEPENDENCIES_NOT_FOUND = set() -def update_all(autotest_dir, add_noncompliant, add_experimental, verbose): - """Function to scan through all tests and add them to the database.""" +def update_all(autotest_dir, add_noncompliant, add_experimental): + """ + Function to scan through all tests and add them to the database. + + This function invoked when no parameters supplied to the command line. + It 'synchronizes' the test database with the current contents of the + client and server test directories. When test code is discovered + in the file system new tests may be added to the db. Likewise, + if test code is not found in the filesystem, tests may be removed + from the db. The base test directories are hard-coded to client/tests, + client/site_tests, server/tests and server/site_tests. + + @param autotest_dir: prepended to path strings (/usr/local/autotest). + @param add_noncompliant: attempt adding test with invalid control files. + @param add_experimental: add tests with experimental attribute set. + """ for path in [ 'server/tests', 'server/site_tests', 'client/tests', 'client/site_tests']: test_path = os.path.join(autotest_dir, path) if not os.path.exists(test_path): continue - if verbose: - print "Scanning " + test_path + logging.info("Scanning %s", test_path) tests = [] tests = get_tests_from_fs(test_path, "^control.*", add_noncompliant=add_noncompliant) update_tests_in_db(tests, add_experimental=add_experimental, add_noncompliant=add_noncompliant, - autotest_dir=autotest_dir, - verbose=verbose) + autotest_dir=autotest_dir) test_suite_path = os.path.join(autotest_dir, 'test_suites') if os.path.exists(test_suite_path): - if verbose: - print "Scanning " + test_suite_path + logging.info("Scanning %s", test_suite_path) tests = get_tests_from_fs(test_suite_path, '.*', add_noncompliant=add_noncompliant) update_tests_in_db(tests, add_experimental=add_experimental, add_noncompliant=add_noncompliant, - autotest_dir=autotest_dir, - verbose=verbose) + autotest_dir=autotest_dir) profilers_path = os.path.join(autotest_dir, "client/profilers") if os.path.exists(profilers_path): - if verbose: - print "Scanning " + profilers_path + logging.info("Scanning %s", profilers_path) profilers = get_tests_from_fs(profilers_path, '.*py$') - update_profilers_in_db(profilers, verbose=verbose, - add_noncompliant=add_noncompliant, + update_profilers_in_db(profilers, add_noncompliant=add_noncompliant, description='NA') # Clean bad db entries - db_clean_broken(autotest_dir, verbose) + db_clean_broken(autotest_dir) -def update_samples(autotest_dir, add_noncompliant, add_experimental, verbose): +def update_samples(autotest_dir, add_noncompliant, add_experimental): + """ + Add only sample tests to the database from the filesystem. + + This function invoked when -S supplied on command line. + Only adds tests to the database - does not delete any. + Samples tests are formatted slightly differently than other tests. + + @param autotest_dir: prepended to path strings (/usr/local/autotest). + @param add_noncompliant: attempt adding test with invalid control files. + @param add_experimental: add tests with experimental attribute set. + """ sample_path = os.path.join(autotest_dir, 'server/samples') if os.path.exists(sample_path): - if verbose: - print "Scanning " + sample_path + logging.info("Scanning %s", sample_path) tests = get_tests_from_fs(sample_path, '.*srv$', add_noncompliant=add_noncompliant) update_tests_in_db(tests, add_experimental=add_experimental, add_noncompliant=add_noncompliant, - autotest_dir=autotest_dir, - verbose=verbose) + autotest_dir=autotest_dir) + +def db_clean_broken(autotest_dir): + """ + Remove tests from autotest_web that do not have valid control files -def db_clean_broken(autotest_dir, verbose): - """Remove tests from autotest_web that do not have valid control files + This function invoked when -c supplied on the command line and when + running update_all(). Removes tests from database which are not + found in the filesystem. Also removes profilers which are just + a special case of tests. - Arguments: - tests: a list of control file relative paths used as keys for deletion. + @param autotest_dir: prepended to path strings (/usr/local/autotest). """ for test in models.Test.objects.all(): full_path = os.path.join(autotest_dir, test.path) if not os.path.isfile(full_path): - if verbose: - print "Removing " + test.path + logging.info("Removing %s", test.path) _log_or_execute(repr(test), test.delete) # Find profilers that are no longer present @@ -109,21 +130,52 @@ full_path = os.path.join(autotest_dir, "client", "profilers", profiler.name) if not os.path.exists(full_path): - if verbose: - print "Removing " + profiler.name + logging.info("Removing %s", profiler.name) _log_or_execute(repr(profiler), profiler.delete) -def update_profilers_in_db(profilers, verbose=False, description='NA', +def db_clean_all(autotest_dir): + """ + Remove all tests from autotest_web - very destructive + + This function invoked when -C supplied on the command line. + Removes ALL tests from the database. + + @param autotest_dir: prepended to path strings (/usr/local/autotest). + """ + for test in models.Test.objects.all(): + full_path = os.path.join(autotest_dir, test.path) + logging.info("Removing %s", test.path) + _log_or_execute(repr(test), test.delete) + + # Find profilers that are no longer present + for profiler in models.Profiler.objects.all(): + full_path = os.path.join(autotest_dir, "client", "profilers", + profiler.name) + logging.info("Removing %s", profiler.name) + _log_or_execute(repr(profiler), profiler.delete) + + +def update_profilers_in_db(profilers, description='NA', add_noncompliant=False): - """Update profilers in autotest_web database""" + """ + Add only profilers to the database from the filesystem. + + This function invoked when -p supplied on command line. + Only adds profilers to the database - does not delete any. + Profilers are formatted slightly differently than tests. + + @param profilers: list of profilers found in the file system. + @param description: simple text to satisfy docstring. + @param add_noncompliant: attempt adding test with invalid control files. + """ for profiler in profilers: name = os.path.basename(profiler).rstrip(".py") if not profilers[profiler]: if add_noncompliant: doc = description else: - print "Skipping %s, missing docstring" % profiler + logging.info("Skipping %s, missing docstring", profiler) else: doc = profilers[profiler] @@ -133,19 +185,35 @@ def update_tests_in_db(tests, dry_run=False, add_experimental=False, - add_noncompliant=False, verbose=False, - autotest_dir=None): - """Update or add each test to the database""" + add_noncompliant=False, autotest_dir=None): + """ + Scans through all tests and add them to the database. + + This function invoked when -t supplied and for update_all. + When test code is discovered in the file system new tests may be added + + @param tests: list of tests found in the filesystem. + @param dry_run: not used at this time. + @param add_experimental: add tests with experimental attribute set. + @param add_noncompliant: attempt adding test with invalid control files. + @param autotest_dir: prepended to path strings (/usr/local/autotest). + """ + site_set_attributes_module = utils.import_site_module( + __file__, 'autotest_lib.utils.site_test_importer_attributes') + for test in tests: new_test = models.Test.objects.get_or_create( path=test.replace(autotest_dir, '').lstrip('/'))[0] - if verbose: - print "Processing " + new_test.path + logging.info("Processing %s", new_test.path) # Set the test's attributes data = tests[test] _set_attributes_clean(new_test, data) + # Custom Attribute Update + if site_set_attributes_module: + site_set_attributes_module._set_attributes_custom(new_test, data) + # This only takes place if --add-noncompliant is provided on the CLI if not new_test.name: test_new_test = test.split('/') @@ -166,8 +234,12 @@ def _set_attributes_clean(test, data): - """Sets the attributes of the Test object""" + """ + First pass sets the attributes of the Test object from file system. + @param test: a test object to be populated for the database. + @param data: object with test data from the file system. + """ test_type = { 'client' : 1, 'server' : 2, } test_time = { 'short' : 1, @@ -209,9 +281,11 @@ def add_label_dependencies(test): """ - Look at the DEPENDENCIES field for each test and add the proper many-to-many - relationships. + Add proper many-to-many relationships from DEPENDENCIES field. + + @param test: test object for the database. """ + # clear out old relationships _log_or_execute(repr(test), test.dependency_labels.clear, subject='clear dependencies from') @@ -232,18 +306,26 @@ def log_dependency_not_found(label_name): + """ + Exception processing when label not found in database. + + @param label_name: from test dependencies. + """ if label_name in DEPENDENCIES_NOT_FOUND: return - print 'Dependency %s not found' % label_name + logging.info("Dependency %s not found", label_name) DEPENDENCIES_NOT_FOUND.add(label_name) def get_tests_from_fs(parent_dir, control_pattern, add_noncompliant=False): - """Find control jobs in location and create one big job - Returns: - dictionary of the form: - tests[file_path] = parsed_object + """ + Find control files in file system and load a list with their info. + @param parent_dir: directory to search recursively. + @param control_pattern: name format of control file. + @param add_noncompliant: ignore control file parse errors. + + @return: dictionary of the form: tests[file_path] = parsed_object """ tests = {} profilers = False @@ -261,7 +343,7 @@ raise_warnings=True) tests[file] = found_test except control_data.ControlVariableException, e: - print "Skipping %s\n%s" % (file, e) + logging.info("Skipping %s\n%s", file, e) pass else: found_test = control_data.parse_control(file) @@ -273,9 +355,15 @@ def recursive_walk(path, wildcard): - """Recurisvely go through a directory. - Returns: - A list of files that match wildcard + """ + Recursively go through a directory. + + This function invoked by get_tests_from_fs(). + + @param path: base directory to start search. + @param wildcard: name format to match. + + @return: A list of files that match wildcard """ files = [] directories = [ path ] @@ -293,29 +381,86 @@ def _log_or_execute(content, func, *args, **kwargs): - """Log a message if dry_run is enabled, or execute the given function + """ + Log a message if dry_run is enabled, or execute the given function. - @param content the actual log message - @param func function to execute if dry_run is not enabled - @param subject (Optional) The type of log being written. Defaults to the - name of the provided function. + Relies on the DRY_RUN global variable. + + @param content: the actual log message. + @param func: function to execute if dry_run is not enabled. + @param subject: (Optional) The type of log being written. Defaults to + the name of the provided function. """ subject = kwargs.get('subject', func.__name__) if DRY_RUN: - print 'Would %s: %s' % (subject, content) + logging.info("Would %s: %s", subject, content) else: func(*args) +def _create_whitelist_set(whitelist_path): + """ + Create a set with contents from a whitelist file for membership testing. + + @param whitelist_path: full path to the whitelist file. + + @return: set with files listed one/line - newlines included. + """ + f = open(whitelist_path, 'r') + whitelist_set = set([line.strip() for line in f]) + f.close() + return whitelist_set + + +def update_from_whitelist(whitelist_set, add_experimental, add_noncompliant, + autotest_dir): + """ + Scans through all tests in the whitelist and add them to the database. + + This function invoked when -w supplied. + + @param whitelist_set: set of tests in full-path form from a whitelist. + @param add_experimental: add tests with experimental attribute set. + @param add_noncompliant: attempt adding test with invalid control files. + @param autotest_dir: prepended to path strings (/usr/local/autotest). + """ + tests = {} + profilers = {} + for file_path in whitelist_set: + if file_path.find('client/profilers') == -1: + try: + found_test = control_data.parse_control(file_path, + raise_warnings=True) + tests[file_path] = found_test + except control_data.ControlVariableException, e: + logging.info("Skipping %s\n%s", file, e) + pass + else: + profilers[file_path] = compiler.parseFile(file_path).doc + + if len(tests) > 0: + update_tests_in_db(tests, add_experimental=add_experimental, + add_noncompliant=add_noncompliant, + autotest_dir=autotest_dir) + if len(profilers) > 0: + update_profilers_in_db(profilers, add_noncompliant=add_noncompliant, + description='NA') + + def main(argv): """Main function""" + global DRY_RUN parser = optparse.OptionParser() - parser.add_option('-c', '--db-clear-tests', - dest='clear_tests', action='store_true', + parser.add_option('-c', '--db-clean-tests', + dest='clean_tests', action='store_true', default=False, - help='Clear client and server tests with invalid control files') + help='Clean client and server tests with invalid control files') + parser.add_option('-C', '--db-clear-all-tests', + dest='clear_all_tests', action='store_true', + default=False, + help='Clear ALL client and server tests') parser.add_option('-d', '--dry-run', dest='dry_run', action='store_true', default=False, help='Dry run for operation') @@ -346,7 +491,9 @@ parser.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, help='Run in verbose mode') - parser.add_option('-z', '--autotest_dir', dest='autotest_dir', + parser.add_option('-w', '--whitelist-file', dest='whitelist_file', + help='Filename for list of test names that must match') + parser.add_option('-z', '--autotest-dir', dest='autotest_dir', default=os.path.join(os.path.dirname(__file__), '..'), help='Autotest directory root') options, args = parser.parse_args() @@ -355,37 +502,63 @@ options.autotest_dir = os.path.abspath(options.autotest_dir) if len(args) > 0: - print "Invalid option(s) provided: ", args + logging.error("Invalid option(s) provided: %s", args) parser.print_help() return 1 - if len(argv) == 1: + if options.verbose: + logging.getLogger().setLevel(logging.DEBUG) + + if len(argv) == 1 or (len(argv) == 2 and options.verbose): update_all(options.autotest_dir, options.add_noncompliant, - options.add_experimental, options.verbose) - db_clean_broken(options.autotest_dir, options.verbose) + options.add_experimental) + db_clean_broken(options.autotest_dir) return 0 + if options.clear_all_tests: + if (options.clean_tests or options.add_all or options.add_samples or + options.add_noncompliant): + logging.error( + "Can only pass --autotest-dir, --dry-run and --verbose with " + "--db-clear-all-tests") + return 1 + db_clean_all(options.autotest_dir) + + whitelist_set = None + if options.whitelist_file: + if options.add_all: + logging.error("Cannot pass both --add-all and --whitelist-file") + return 1 + whitelist_path = os.path.abspath(options.whitelist_file) + if not os.path.isfile(whitelist_path): + logging.error("--whitelist-file (%s) not found", whitelist_path) + return 1 + logging.info("Using whitelist file %s", whitelist_path) + whitelist_set = _create_whitelist_set(whitelist_path) + update_from_whitelist(whitelist_set, + add_experimental=options.add_experimental, + add_noncompliant=options.add_noncompliant, + autotest_dir=options.autotest_dir) if options.add_all: update_all(options.autotest_dir, options.add_noncompliant, - options.add_experimental, options.verbose) + options.add_experimental) if options.add_samples: update_samples(options.autotest_dir, options.add_noncompliant, - options.add_experimental, options.verbose) - if options.clear_tests: - db_clean_broken(options.autotest_dir, options.verbose) + options.add_experimental) if options.tests_dir: options.tests_dir = os.path.abspath(options.tests_dir) tests = get_tests_from_fs(options.tests_dir, options.control_pattern, add_noncompliant=options.add_noncompliant) update_tests_in_db(tests, add_experimental=options.add_experimental, add_noncompliant=options.add_noncompliant, - autotest_dir=options.autotest_dir, - verbose=options.verbose) + autotest_dir=options.autotest_dir) if options.profile_dir: profilers = get_tests_from_fs(options.profile_dir, '.*py$') - update_profilers_in_db(profilers, verbose=options.verbose, + update_profilers_in_db(profilers, add_noncompliant=options.add_noncompliant, description='NA') + if options.clean_tests: + db_clean_broken(options.autotest_dir) if __name__ == "__main__": _______________________________________________ Autotest mailing list [email protected] http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
