| OLD | NEW |
| 1 #!/usr/bin/python | 1 #!/usr/bin/python |
| 2 """ | 2 """ |
| 3 KVM configuration file utility functions. | 3 KVM configuration file utility functions. |
| 4 | 4 |
| 5 @copyright: Red Hat 2008-2010 | 5 @copyright: Red Hat 2008-2010 |
| 6 """ | 6 """ |
| 7 | 7 |
| 8 import logging, re, os, sys, optparse, array, traceback, cPickle | 8 import logging, re, os, sys, optparse, array, traceback, cPickle |
| 9 import common | 9 import common |
| 10 import kvm_utils | 10 import kvm_utils |
| 11 from autotest_lib.client.common_lib import error | 11 from autotest_lib.client.common_lib import error |
| 12 from autotest_lib.client.common_lib import logging_config, logging_manager | 12 from autotest_lib.client.common_lib import logging_manager |
| 13 | 13 |
| 14 | 14 |
| 15 class config: | 15 class config: |
| 16 """ | 16 """ |
| 17 Parse an input file or string that follows the KVM Test Config File format | 17 Parse an input file or string that follows the KVM Test Config File format |
| 18 and generate a list of dicts that will be later used as configuration | 18 and generate a list of dicts that will be later used as configuration |
| 19 parameters by the KVM tests. | 19 parameters by the KVM tests. |
| 20 | 20 |
| 21 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File | 21 @see: http://www.linux-kvm.org/page/KVM-Autotest/Test_Config_File |
| 22 """ | 22 """ |
| 23 | 23 |
| 24 def __init__(self, filename=None, debug=True): | 24 def __init__(self, filename=None, debug=True): |
| 25 """ | 25 """ |
| 26 Initialize the list and optionally parse a file. | 26 Initialize the list and optionally parse a file. |
| 27 | 27 |
| 28 @param filename: Path of the file that will be taken. | 28 @param filename: Path of the file that will be taken. |
| 29 @param debug: Whether to turn on debugging output. | 29 @param debug: Whether to turn on debugging output. |
| 30 """ | 30 """ |
| 31 self.list = [array.array("H", [4, 4, 4, 4])] | 31 self.list = [array.array("H", [4, 4, 4, 4])] |
| 32 self.object_cache = [] | 32 self.object_cache = [] |
| 33 self.object_cache_indices = {} | 33 self.object_cache_indices = {} |
| 34 self.regex_cache = {} | 34 self.regex_cache = {} |
| 35 self.filename = filename | |
| 36 self.debug = debug | 35 self.debug = debug |
| 37 if filename: | 36 if filename: |
| 38 self.parse_file(filename) | 37 self.parse_file(filename) |
| 39 | 38 |
| 40 | 39 |
| 41 def parse_file(self, filename): | 40 def parse_file(self, filename): |
| 42 """ | 41 """ |
| 43 Parse file. If it doesn't exist, raise an IOError. | 42 Parse file. If it doesn't exist, raise an IOError. |
| 44 | 43 |
| 45 @param filename: Path of the configuration file. | 44 @param filename: Path of the configuration file. |
| 46 """ | 45 """ |
| 47 if not os.path.exists(filename): | 46 if not os.path.exists(filename): |
| 48 raise IOError("File %s not found" % filename) | 47 raise IOError("File %s not found" % filename) |
| 49 self.filename = filename | |
| 50 str = open(filename).read() | 48 str = open(filename).read() |
| 51 self.list = self.parse(configreader(str), self.list) | 49 self.list = self.parse(configreader(filename, str), self.list) |
| 52 | 50 |
| 53 | 51 |
| 54 def parse_string(self, str): | 52 def parse_string(self, str): |
| 55 """ | 53 """ |
| 56 Parse a string. | 54 Parse a string. |
| 57 | 55 |
| 58 @param str: String to parse. | 56 @param str: String to parse. |
| 59 """ | 57 """ |
| 60 self.list = self.parse(configreader(str), self.list) | 58 self.list = self.parse(configreader('<string>', str, real_file=False), s
elf.list) |
| 61 | 59 |
| 62 | 60 |
| 63 def fork_and_parse(self, filename=None, str=None): | 61 def fork_and_parse(self, filename=None, str=None): |
| 64 """ | 62 """ |
| 65 Parse a file and/or a string in a separate process to save memory. | 63 Parse a file and/or a string in a separate process to save memory. |
| 66 | 64 |
| 67 Python likes to keep memory to itself even after the objects occupying | 65 Python likes to keep memory to itself even after the objects occupying |
| 68 it have been destroyed. If during a call to parse_file() or | 66 it have been destroyed. If during a call to parse_file() or |
| 69 parse_string() a lot of memory is used, it can only be freed by | 67 parse_string() a lot of memory is used, it can only be freed by |
| 70 terminating the process. This function works around the problem by | 68 terminating the process. This function works around the problem by |
| (...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 290 "context, %d remain)" % | 288 "context, %d remain)" % |
| 291 (len_list, len(list))) | 289 (len_list, len(list))) |
| 292 continue | 290 continue |
| 293 | 291 |
| 294 # Parse 'variants' | 292 # Parse 'variants' |
| 295 if line == "variants:": | 293 if line == "variants:": |
| 296 # 'variants' not allowed in restricted mode | 294 # 'variants' not allowed in restricted mode |
| 297 # (inside an exception or inside subvariants) | 295 # (inside an exception or inside subvariants) |
| 298 if restricted: | 296 if restricted: |
| 299 e_msg = "Using variants in this context is not allowed" | 297 e_msg = "Using variants in this context is not allowed" |
| 300 raise error.AutotestError(e_msg) | 298 cr.raise_error(e_msg) |
| 301 if self.debug and not restricted: | 299 if self.debug and not restricted: |
| 302 _debug_print(indented_line, | 300 _debug_print(indented_line, |
| 303 "Entering variants block (%d dicts in " | 301 "Entering variants block (%d dicts in " |
| 304 "current context)" % len_list) | 302 "current context)" % len_list) |
| 305 list = self.parse_variants(cr, list, subvariants=False, | 303 list = self.parse_variants(cr, list, subvariants=False, |
| 306 prev_indent=indent) | 304 prev_indent=indent) |
| 307 continue | 305 continue |
| 308 | 306 |
| 309 # Parse 'subvariants' (the block is parsed for each dict | 307 # Parse 'subvariants' (the block is parsed for each dict |
| 310 # separately) | 308 # separately) |
| (...skipping 19 matching lines...) Expand all Loading... |
| 330 prev_indent=indent) | 328 prev_indent=indent) |
| 331 list = new_list | 329 list = new_list |
| 332 continue | 330 continue |
| 333 | 331 |
| 334 # Parse 'include' statements | 332 # Parse 'include' statements |
| 335 if words[0] == "include": | 333 if words[0] == "include": |
| 336 if len(words) <= 1: | 334 if len(words) <= 1: |
| 337 continue | 335 continue |
| 338 if self.debug and not restricted: | 336 if self.debug and not restricted: |
| 339 _debug_print(indented_line, "Entering file %s" % words[1]) | 337 _debug_print(indented_line, "Entering file %s" % words[1]) |
| 340 if self.filename: | 338 |
| 341 filename = os.path.join(os.path.dirname(self.filename), | 339 cur_filename = cr.real_filename() |
| 342 words[1]) | 340 if cur_filename is None: |
| 343 if os.path.exists(filename): | 341 cr.raise_error("'include' is valid only when parsing a file"
) |
| 344 str = open(filename).read() | 342 |
| 345 list = self.parse(configreader(str), list, restricted) | 343 filename = os.path.join(os.path.dirname(cur_filename), |
| 346 if self.debug and not restricted: | 344 words[1]) |
| 347 _debug_print("", "Leaving file %s" % words[1]) | 345 if not os.path.exists(filename): |
| 348 else: | 346 cr.raise_error("Cannot include %s -- file not found" % (file
name)) |
| 349 logging.warning("Cannot include %s -- file not found", | 347 |
| 350 filename) | 348 str = open(filename).read() |
| 351 else: | 349 list = self.parse(configreader(filename, str), list, restricted) |
| 352 logging.warning("Cannot include %s because no file is " | 350 if self.debug and not restricted: |
| 353 "currently open", words[1]) | 351 _debug_print("", "Leaving file %s" % words[1]) |
| 352 |
| 354 continue | 353 continue |
| 355 | 354 |
| 356 # Parse multi-line exceptions | 355 # Parse multi-line exceptions |
| 357 # (the block is parsed for each dict separately) | 356 # (the block is parsed for each dict separately) |
| 358 if line.endswith(":"): | 357 if line.endswith(":"): |
| 359 if self.debug and not restricted: | 358 if self.debug and not restricted: |
| 360 _debug_print(indented_line, | 359 _debug_print(indented_line, |
| 361 "Entering multi-line exception block " | 360 "Entering multi-line exception block " |
| 362 "(%d dicts in current context outside " | 361 "(%d dicts in current context outside " |
| 363 "exception)" % len_list) | 362 "exception)" % len_list) |
| (...skipping 168 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 532 | 531 |
| 533 # configreader | 532 # configreader |
| 534 | 533 |
| 535 class configreader: | 534 class configreader: |
| 536 """ | 535 """ |
| 537 Preprocess an input string and provide file-like services. | 536 Preprocess an input string and provide file-like services. |
| 538 This is intended as a replacement for the file and StringIO classes, | 537 This is intended as a replacement for the file and StringIO classes, |
| 539 whose readline() and/or seek() methods seem to be slow. | 538 whose readline() and/or seek() methods seem to be slow. |
| 540 """ | 539 """ |
| 541 | 540 |
| 542 def __init__(self, str): | 541 def __init__(self, filename, str, real_file=True): |
| 543 """ | 542 """ |
| 544 Initialize the reader. | 543 Initialize the reader. |
| 545 | 544 |
| 545 @param filename: the filename we're parsing |
| 546 @param str: The string to parse. | 546 @param str: The string to parse. |
| 547 @param real_file: Indicates if filename represents a real file. Defaults
to True. |
| 547 """ | 548 """ |
| 549 self.filename = filename |
| 550 self.is_real_file = real_file |
| 548 self.line_index = 0 | 551 self.line_index = 0 |
| 549 self.lines = [] | 552 self.lines = [] |
| 550 for line in str.splitlines(): | 553 self.real_number = [] |
| 554 for num, line in enumerate(str.splitlines()): |
| 551 line = line.rstrip().expandtabs() | 555 line = line.rstrip().expandtabs() |
| 552 stripped_line = line.strip() | 556 stripped_line = line.strip() |
| 553 indent = len(line) - len(stripped_line) | 557 indent = len(line) - len(stripped_line) |
| 554 if (not stripped_line | 558 if (not stripped_line |
| 555 or stripped_line.startswith("#") | 559 or stripped_line.startswith("#") |
| 556 or stripped_line.startswith("//")): | 560 or stripped_line.startswith("//")): |
| 557 continue | 561 continue |
| 558 self.lines.append((line, stripped_line, indent)) | 562 self.lines.append((line, stripped_line, indent)) |
| 563 self.real_number.append(num + 1) |
| 559 | 564 |
| 560 | 565 |
| 566 def real_filename(self): |
| 567 """Returns the filename we're reading, in case it is a real file |
| 568 |
| 569 @returns the filename we are parsing, or None in case we're not parsing
a real file |
| 570 """ |
| 571 if self.is_real_file: |
| 572 return self.filename |
| 573 |
| 561 def get_next_line(self): | 574 def get_next_line(self): |
| 562 """ | 575 """ |
| 563 Get the next non-empty, non-comment line in the string. | 576 Get the next non-empty, non-comment line in the string. |
| 564 | 577 |
| 565 @param file: File like object. | 578 @param file: File like object. |
| 566 @return: (line, stripped_line, indent), where indent is the line's | 579 @return: (line, stripped_line, indent), where indent is the line's |
| 567 indent level or -1 if no line is available. | 580 indent level or -1 if no line is available. |
| 568 """ | 581 """ |
| 569 try: | 582 try: |
| 570 if self.line_index < len(self.lines): | 583 if self.line_index < len(self.lines): |
| (...skipping 10 matching lines...) Expand all Loading... |
| 581 """ | 594 """ |
| 582 return self.line_index | 595 return self.line_index |
| 583 | 596 |
| 584 | 597 |
| 585 def seek(self, index): | 598 def seek(self, index): |
| 586 """ | 599 """ |
| 587 Set the current line index. | 600 Set the current line index. |
| 588 """ | 601 """ |
| 589 self.line_index = index | 602 self.line_index = index |
| 590 | 603 |
| 604 def raise_error(self, msg): |
| 605 """Raise an error related to the last line returned by get_next_line() |
| 606 """ |
| 607 if self.line_index == 0: # nothing was read. shouldn't happen, but... |
| 608 line_id = 'BEGIN' |
| 609 elif self.line_index >= len(self.lines): # past EOF |
| 610 line_id = 'EOF' |
| 611 else: |
| 612 # line_index is the _next_ line. get the previous one |
| 613 line_id = str(self.real_number[self.line_index-1]) |
| 614 raise error.AutotestError("%s:%s: %s" % (self.filename, line_id, msg)) |
| 615 |
| 591 | 616 |
| 592 # Array structure: | 617 # Array structure: |
| 593 # ---------------- | 618 # ---------------- |
| 594 # The first 4 elements contain the indices of the 4 segments. | 619 # The first 4 elements contain the indices of the 4 segments. |
| 595 # a[0] -- Index of beginning of 'name' segment (always 4). | 620 # a[0] -- Index of beginning of 'name' segment (always 4). |
| 596 # a[1] -- Index of beginning of 'shortname' segment. | 621 # a[1] -- Index of beginning of 'shortname' segment. |
| 597 # a[2] -- Index of beginning of 'depend' segment. | 622 # a[2] -- Index of beginning of 'depend' segment. |
| 598 # a[3] -- Index of beginning of 'content' segment. | 623 # a[3] -- Index of beginning of 'content' segment. |
| 599 # The next elements in the array comprise the aforementioned segments: | 624 # The next elements in the array comprise the aforementioned segments: |
| 600 # The 'name' segment begins with a[a[0]] and ends with a[a[1]-1]. | 625 # The 'name' segment begins with a[a[0]] and ends with a[a[1]-1]. |
| (...skipping 74 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 675 | 700 |
| 676 | 701 |
| 677 if __name__ == "__main__": | 702 if __name__ == "__main__": |
| 678 parser = optparse.OptionParser("usage: %prog [options] [filename]") | 703 parser = optparse.OptionParser("usage: %prog [options] [filename]") |
| 679 parser.add_option('--verbose', dest="debug", action='store_true', | 704 parser.add_option('--verbose', dest="debug", action='store_true', |
| 680 help='include debug messages in console output') | 705 help='include debug messages in console output') |
| 681 | 706 |
| 682 options, args = parser.parse_args() | 707 options, args = parser.parse_args() |
| 683 debug = options.debug | 708 debug = options.debug |
| 684 if args: | 709 if args: |
| 685 filename = args[0] | 710 filenames = args |
| 686 else: | 711 else: |
| 687 filename = os.path.join(os.path.dirname(sys.argv[0]), "tests.cfg") | 712 filenames = [os.path.join(os.path.dirname(sys.argv[0]), "tests.cfg")] |
| 688 | 713 |
| 689 # Here we configure the stand alone program to use the autotest | 714 # Here we configure the stand alone program to use the autotest |
| 690 # logging system. | 715 # logging system. |
| 691 logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(), | 716 logging_manager.configure_logging(kvm_utils.KvmLoggingConfig(), |
| 692 verbose=debug) | 717 verbose=debug) |
| 693 dicts = config(filename, debug=debug).get_generator() | 718 cfg = config(debug=debug) |
| 719 for fn in filenames: |
| 720 cfg.parse_file(fn) |
| 721 dicts = cfg.get_generator() |
| 694 for i, dict in enumerate(dicts): | 722 for i, dict in enumerate(dicts): |
| 695 logging.info("Dictionary #%d:", i) | 723 print "Dictionary #%d:" % (i) |
| 696 keys = dict.keys() | 724 keys = dict.keys() |
| 697 keys.sort() | 725 keys.sort() |
| 698 for key in keys: | 726 for key in keys: |
| 699 logging.info(" %s = %s", key, dict[key]) | 727 print " %s = %s" % (key, dict[key]) |
| OLD | NEW |