1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20  from   calendar import timegm 
 21  import ConfigParser 
 22  import imp 
 23  import lock 
 24  import os 
 25  import os.path 
 26  import qm 
 27  import re 
 28  import string 
 29  import sys 
 30  import tempfile 
 31  import time 
 32  import traceback 
 33  import types 
 34  import getpass 
 35  import StringIO 
 36  import htmllib 
 37  import formatter 
 38  if sys.platform != "win32": 
 39      import fcntl 
 40       
 41   
 42   
 43   
 44   
 45  program_name = None 
 46  """The name of the application program.""" 
 47   
 48   
 49   
 50   
 51   
 53      """An exception generated directly by QM. 
 54   
 55      All exceptions thrown by QM should be derived from this class.""" 
 56   
 58          """Construct a new 'QMException'. 
 59   
 60          'message' -- A string describing the cause of the message as 
 61          structured text.  If this exception is not handled, the 
 62          'message' will be displayed as an error message.""" 
 63   
 64          Exception.__init__(self, message) 
   65   
 69   
 70   
 74   
 75   
 76   
 78      """A 'PythonException' is a wrapper around a Python exception. 
 79   
 80      A 'PythonException' is a 'QMException' and, as such, can be 
 81      processed by the QM error-handling routines.  However, the raw 
 82      Python exception which triggered this exception can be obtained by 
 83      using the 'exc_type' and 'exc_value' attributes of this 
 84      exception.""" 
 85   
 86 -    def __init__(self, message, exc_type, exc_value): 
  87          """Construct a new 'PythonException'. 
 88   
 89          'message' -- A string describing the cause of the message as 
 90          structured text.  If this exception is not handled, the 
 91          'message' will be displayed as an error message. 
 92   
 93          'exc_type' -- The type of the Python exception. 
 94   
 95          'exc_value' -- The value of the Python exception.""" 
 96           
 97          QMException.__init__(self, message) 
 98   
 99          self.exc_type = exc_type 
100          self.exc_value = exc_value 
  101       
102   
103   
104   
105   
107      """Interface object to QM configuration files. 
108   
109      Configuration files are in the format parsed by the standard 
110      'ConfigParser' module, namely 'win.ini'--style files.""" 
111   
112      user_rc_file_name = ".qmrc" 
113      """The name of the user configuration file.""" 
114   
115   
117          """Create a new configuration instance.""" 
118   
119          ConfigParser.ConfigParser.__init__(self) 
120          if os.environ.has_key("HOME"): 
121              home_directory = os.environ["HOME"] 
122              rc_file = os.path.join(home_directory, self.user_rc_file_name) 
123               
124               
125               
126              self.read(rc_file) 
 127   
128   
129 -    def Load(self, section): 
 130          """Load configuration. 
131   
132          'section' -- The configuration section from which subsequent 
133          variables are loaded.""" 
134   
135          self.__section = section 
 136   
137   
138 -    def Get(self, option, default, section=None): 
 139          """Retrieve a configuration variable. 
140   
141          'option' -- The name of the option to retrieve. 
142   
143          'default' -- The default value to return if the option is not 
144          found. 
145   
146          'section' -- The section from which to retrieve the option. 
147          'None' indicates the section specified to the 'Load' method for 
148          this instance.   
149   
150          precondition -- The RC configuration must be loaded.""" 
151   
152           
153           
154          if section is None: 
155              section = self.__section 
156   
157          try: 
158               
159              return self.get(section, option) 
160          except ConfigParser.NoSectionError: 
161               
162              return default 
163          except ConfigParser.NoOptionError: 
164               
165              return default 
 166   
167   
169          """Return a sequence of options. 
170   
171          'section' -- The section for which to list options, or 'None' 
172          for the section specified to 'Load'. 
173   
174          precondition -- The RC configuration must be loaded.""" 
175   
176           
177           
178          if section is None: 
179              section = self.__section 
180          try: 
181              options = self.options(section) 
182          except ConfigParser.NoSectionError: 
183               
184              return [] 
185          else: 
186               
187               
188              if "__name__" in options: 
189                  options.remove("__name__") 
190              return options 
  191   
192       
193   
194   
195   
196   
198      """Return the path to a file in the QM library directory.""" 
199   
200       
201       
202      return os.path.join(os.path.dirname(__file__), *components) 
 203   
204   
206      """Return the path to a file in the QM data file directory.""" 
207   
208      return os.path.join(qm.prefix, qm.data_dir, *components) 
 209   
210   
212      """Return a path to a file in the QM documentation file directory.""" 
213   
214      return os.path.join(qm.prefix, qm.doc_dir, *components) 
 215   
216   
230   
231   
241   
242   
244      """Replace CRLF with LF in 'text'.""" 
245   
246      return string.replace(text, "\r\n", "\n") 
 247   
248   
249  __load_module_lock = lock.RLock() 
250  """A lock used by load_module.""" 
251   
252 -def load_module(name, search_path=sys.path, load_path=sys.path): 
 253      """Load a Python module. 
254   
255      'name' -- The fully-qualified name of the module to load, for 
256      instance 'package.subpackage.module'. 
257   
258      'search_path' -- A sequence of directories.  These directories are 
259      searched to find the module. 
260   
261      'load_path' -- The setting of 'sys.path' when the module is loaded. 
262       
263      returns -- A module object. 
264   
265      raises -- 'ImportError' if the module cannot be found.""" 
266   
267       
268       
269       
270       
271   
272       
273       
274       
275      __load_module_lock.acquire() 
276      try: 
277           
278          module = sys.modules.get(name) 
279          if module: 
280              return module 
281   
282           
283           
284          components = string.split(name, ".") 
285          if len(components) > 1: 
286               
287               
288              parent_package = string.join(components[:-1], ".") 
289               
290              package = load_module(parent_package, search_path, load_path) 
291               
292              search_path = package.__path__ 
293          else: 
294               
295              package = None 
296           
297           
298          module_name = components[-1] 
299           
300          file, file_name, description = imp.find_module(module_name, 
301                                                         search_path) 
302           
303          try: 
304               
305               
306               
307               
308              old_python_path = sys.path[:] 
309              sys.path = load_path + sys.path 
310               
311              try: 
312                  module = imp.load_module(name, file, file_name, description) 
313              except: 
314                   
315                  if sys.modules.has_key(name): 
316                      del sys.modules[name] 
317                  raise 
318               
319              sys.path = old_python_path 
320               
321               
322              if package is not None: 
323                  setattr(package, module_name, module) 
324              return module 
325          finally: 
326               
327              if file is not None: 
328                  file.close() 
329      finally: 
330           
331          __load_module_lock.release() 
 332   
333           
334 -def load_class(name, search_path = sys.path, load_path = sys.path): 
 335      """Load a Python class. 
336   
337      'name' -- The fully-qualified (including package and module names) 
338      class name, for instance 'package.subpackage.module.MyClass'.  The 
339      class must be at the top level of the module's namespace, i.e. not 
340      nested in another class. 
341   
342      'search_path' -- A sequence of directories.  These directories are 
343      searched to find the module. 
344   
345      'load_path' -- The setting of 'sys.path' when the module is loaded. 
346   
347      returns -- A class object. 
348   
349      raises -- 'ImportError' if the module containing the class can't be 
350      imported, or if there is no class with the specified name in that 
351      module, or if 'name' doesn't correspond to a class.""" 
352   
353       
354       
355       
356      if not "." in name: 
357          raise QMException, \ 
358                "%s is not a fully-qualified class name" % name 
359       
360      components = string.split(name, ".") 
361       
362      module_name = string.join(components[:-1], ".") 
363       
364      class_name = components[-1] 
365       
366      module = load_module(module_name, search_path, load_path) 
367       
368      try: 
369          klass = module.__dict__[class_name] 
370           
371           
372           
373           
374          if (not isinstance(klass, types.ClassType) 
375              and not issubclass(klass, object)): 
376               
377              raise QMException, "%s is not a class" % name 
378          return klass 
379      except KeyError: 
380           
381          raise QMException, \ 
382                "no class named %s in module %s" % (class_name, module_name) 
 383       
384       
386      """Split 'path' into components. 
387   
388      Uses 'os.path.split' recursively on the directory components of 
389      'path' to separate all path components. 
390   
391      'path' -- The path to split. 
392   
393      returns -- A list of path componets.""" 
394   
395      dir, entry = os.path.split(path) 
396      if dir == "" or dir == os.sep: 
397          return [ entry ] 
398      else: 
399          return split_path_fully(dir) + [ entry ] 
 400   
401   
403      """Create and open a temporary file. 
404   
405      'suffix' -- The last part of the temporary file name, as for 
406      Python's 'mktemp' function. 
407       
408      The file is open for reading and writing.  The caller is responsible 
409      for deleting the file when finished with it. 
410   
411      returns -- A pair '(file_name, file_descriptor)' for the temporary 
412      file.""" 
413   
414      file_name = tempfile.mktemp(suffix) 
415   
416      try: 
417           
418          fd = os.open(file_name, 
419                       os.O_CREAT | os.O_EXCL | os.O_RDWR, 
420                       0600) 
421      except: 
422          exc_info = sys.exc_info() 
423          raise QMException, \ 
424                qm.error("temp file error", 
425                         file_name=file_name, 
426                         exc_class=str(exc_info[0]), 
427                         exc_arg=str(exc_info[1])) 
428      return (file_name, fd) 
 429   
430   
432      """Create and open a temporary file. 
433   
434      'mode' -- The mode argument to pass to 'fopen'. 
435       
436      'suffix' -- The last part of the temporary file name, as for 
437      Python's 'mktemp' function. 
438       
439      Like 'open_temporary_file_fd', except that the second element of the 
440      return value is a file object.""" 
441   
442       
443       
444      file_name, fd = open_temporary_file_fd(suffix) 
445      return (file_name, os.fdopen(fd, mode)) 
 446   
447   
449      """Prevent 'fd' from being inherited across 'exec'. 
450       
451      'fd' -- A file descriptor, or object providing a 'fileno()' 
452      method. 
453   
454      This function has no effect on Windows.""" 
455   
456      if sys.platform != "win32": 
457          flags = fcntl.fcntl(fd, fcntl.F_GETFD) 
458          try: 
459              flags |= fcntl.FD_CLOEXEC 
460          except AttributeError: 
461               
462               
463               
464              flags |= 1 
465          fcntl.fcntl(fd, fcntl.F_SETFD, flags) 
 466   
467   
469      """Make a best-effort attempt to copy 'object'. 
470   
471      returns -- A copy of 'object', if feasible, or otherwise 
472      'object'.""" 
473   
474      if type(object) is types.ListType: 
475           
476          return object[:] 
477      elif type(object) is types.DictionaryType: 
478           
479          return object.copy() 
480      elif type(object) is types.InstanceType: 
481           
482           
483          copy_function = getattr(object, "copy", None) 
484          if callable(copy_function): 
485              return object.copy() 
486          else: 
487              return object 
488      else: 
489           
490          return object 
 491   
492   
493 -def wrap_lines(text, columns=72, break_delimiter="\\", indent=""): 
 494      """Wrap lines in 'text' to 'columns' columns. 
495   
496      'text' -- The text to wrap. 
497   
498      'columns' -- The maximum number of columns of text. 
499   
500      'break_delimiter' -- Text to place at the end of each broken line 
501      (may be an empty string). 
502   
503      'indent' -- Text to place at the start of each line.  The length of 
504      'indent' does not count towards 'columns'. 
505   
506      returns -- The wrapped text.""" 
507   
508       
509      lines = string.split(text, "\n") 
510       
511       
512      new_length = columns - len(break_delimiter) 
513       
514      for index in range(0, len(lines)): 
515          line = lines[index] 
516           
517          if len(line) > columns: 
518               
519              breaks = len(line) / new_length 
520              new_line = "" 
521               
522              while breaks > 0: 
523                  new_line = new_line \ 
524                             + line[:new_length] \ 
525                             + break_delimiter \ 
526                             + "\n" + indent 
527                  line = line[new_length:] 
528                  breaks = breaks - 1 
529              new_line = new_line + line 
530               
531              lines[index] = new_line 
532       
533      lines = map(lambda l, i=indent: i + l, lines) 
534       
535      return string.join(lines, "\n") 
 536   
537   
562   
563   
581   
582   
584      """Parse a ISO8601-compliant formatted date and time. 
585   
586      See also 'format_time_iso'. 
587   
588      'time_string' -- The string to be parsed, as returned by 
589      e.g. 'format_time_iso'. 
590   
591      returns -- The time as a float, like that returned by 
592      'time.time'.""" 
593   
594      return time.mktime(time.strptime(time_string, "%Y-%m-%dT%H:%M:%SZ")) 
 595   
596   
605   
606   
608      """Split a command into an argument list. 
609   
610      'command' -- A string containing a shell or similar command. 
611   
612      returns -- An argument list obtained by splitting the command.""" 
613   
614       
615      command = string.strip(command) 
616       
617      argument_list = re.split(" +", command) 
618      return argument_list 
 619   
620   
622      """Parse a boolean string. 
623   
624      'value' -- A string. 
625   
626      returns -- True if 'value' is a true string, false if 'value' is a 
627      false string. 
628   
629      raises -- 'ValueError' if 'value' is neither a true string, nor a 
630      false string.""" 
631   
632      value = value.lower() 
633      if value in ("1", "true", "yes", "on"): 
634          return 1 
635      elif value in ("0", "false", "no", "off"): 
636          return 0 
637      else: 
638          raise ValueError, value 
 639   
640       
642      """Parse a string list. 
643   
644      'value' -- A string. 
645   
646      returns -- A list of strings. 
647   
648      raises -- 'ValueError' if 'value' contains unbalanced quotes.""" 
649   
650       
651      if "'" not in value and '"' not in value: 
652          return value.split() 
653       
654      breaks = [] 
655      esc = False 
656      quoted_1 = False  
657      quoted_2 = False  
658      value.strip() 
659       
660      for i, c in enumerate(value): 
661          if c == '\\': 
662              esc = not esc 
663              continue 
664          elif c == "'": 
665              if not esc and not quoted_2: 
666                  quoted_1 = not quoted_1 
667          elif c == '"': 
668              if not esc and not quoted_1: 
669                  quoted_2 = not quoted_2 
670          elif c in [' ', '\t']: 
671               
672              if not (quoted_1 or quoted_2 or esc): 
673                  breaks.append(i) 
674          esc = False 
675       
676      if quoted_1 or quoted_2 or esc: 
677          raise ValueError, value 
678      string_list = [] 
679      start = 0 
680      for end in breaks: 
681          string_list.append(value[start:end]) 
682          start = end 
683      string_list.append(value[start:]) 
684      return [s.strip() for s in string_list if s not in [' ', '\t']]  
 685   
686       
687   
688   
689   
690   
691 -def parse_time(time_string, default_local_time_zone=1): 
 692      """Parse a date and/or time string. 
693   
694      'time_string' -- A string representing a date and time in the format 
695      returned by 'format_time'.  This function makes a best-effort 
696      attempt to parse incomplete strings as well. 
697   
698      'default_local_time_zone' -- If the time zone is not specified in 
699      'time_string' and this parameter is true, assume the time is in the 
700      local time zone.  If this parameter is false, assume the time is 
701      UTC. 
702   
703      returns -- An integer number of seconds since the start of the UNIX 
704      epoch, UTC. 
705   
706      Only UTC and the current local time zone may be specified explicitly 
707      in 'time_string'.""" 
708   
709       
710      time_string = string.strip(time_string) 
711      time_string = re.sub(" +", " ", time_string) 
712      time_string = re.sub("/", "-", time_string) 
713       
714       
715       
716      time_string = re.sub("GMT Standard Time", "UTC", time_string) 
717       
718      components = string.split(time_string, " ") 
719   
720       
721      if components[-1] == "UTC": 
722           
723          utc = 1 
724          dst = 0 
725          components.pop() 
726      elif components[-1] == time.tzname[0]: 
727           
728          utc = 0 
729          dst = 0 
730          components.pop() 
731      elif time.daylight and components[-1] == time.tzname[1]: 
732           
733          utc = 0 
734          dst = 1 
735          components.pop() 
736      else: 
737           
738          if default_local_time_zone: 
739              utc = 0 
740              dst = -1 
741          else: 
742              utc = 1 
743              dst = 0 
744   
745       
746      if utc: 
747          time_tuple = time.gmtime(time.time()) 
748      else: 
749          time_tuple = time.localtime(time.time()) 
750       
751      year, month, day = time_tuple[:3] 
752       
753      hour = 0 
754      minute = 0 
755   
756       
757      for component in components: 
758          if string.count(component, "-") == 2: 
759               
760              year, month, day = map(int, string.split(component, "-")) 
761          elif string.count(component, ":") in [1, 2]: 
762               
763              hour, minute = map(int, string.split(component, ":")[:2]) 
764          else: 
765               
766              raise ValueError 
767           
768       
769      time_tuple = (year, month, day, hour, minute, 0, 0, 0, dst) 
770       
771      if utc: 
772          return int(timegm(time_tuple)) 
773      else: 
774          return int(time.mktime(time_tuple)) 
 775       
776   
778      """Parse an 'assignment' of the form 'name=value'. 
779   
780      'aassignment' -- A string.  The string should have the form 
781      'name=value'. 
782   
783      returns -- A pair '(name, value)'.""" 
784   
785       
786      try: 
787          (name, value) = string.split(assignment, "=", 1) 
788          return (name, value) 
789      except: 
790          raise QMException, \ 
791                qm.error("invalid keyword assignment", 
792                         argument=assignment) 
 793   
794   
796      """Read assignments from a 'file'. 
797   
798      'file' -- A file object containing the context.  When the file is 
799      read, leading and trailing whitespace is discarded from each line 
800      in the file.  Then, lines that begin with a '#' and lines that 
801      contain no characters are discarded.  All other lines must be of 
802      the form 'NAME=VALUE' and indicate an assignment to the context 
803      variable 'NAME' of the indicated 'VALUE'. 
804   
805      returns -- A dictionary mapping each of the indicated 'NAME's to its 
806      corresponding 'VALUE'.  If multiple assignments to the same 'NAME' 
807      are present, only the 'VALUE' from the last assignment is stored.""" 
808   
809       
810      assignments = {} 
811       
812       
813      lines = file.readlines() 
814       
815      lines = map(string.strip, lines) 
816       
817       
818      lines = filter(lambda x: x != "" and not x.startswith("#"), 
819                     lines) 
820       
821      for line in lines: 
822           
823          (name, value) = parse_assignment(line) 
824           
825          assignments[name] = value 
826   
827      return assignments 
 828   
829   
831      """Returns the current username as a string. 
832   
833      This is our best guess as to the username of the user who is 
834      actually logged in, as opposed to the effective user id used for 
835      running tests. 
836   
837      If the username cannot be found, raises a 'QMException'.""" 
838   
839       
840      try: 
841          return getpass.getuser() 
842      except: 
843          pass 
844   
845       
846       
847      try: 
848          import win32api 
849      except ImportError: 
850          pass 
851      else: 
852          try: 
853              return win32api.GetUserName() 
854          except: 
855              raise PythonException("Error accessing win32 user database", 
856                                    *sys.exc_info()[:2]) 
857   
858       
859      raise QMException, "Cannot determine user name." 
 860   
861   
863      """Returns the current user id as an integer. 
864   
865      This is the real user id, not the effective user id, to better track 
866      who is actually running the tests. 
867   
868      If the user id cannot be found or is not defined, raises a 
869      'QMException'.""" 
870   
871      try: 
872          uid = os.getuid() 
873      except AttributeError: 
874          raise QMException, "User ids not supported on this system." 
875      return uid 
 876       
877   
878 -def html_to_text(html, width=72): 
 879      """Renders HTML to text in a simple way. 
880   
881      'html' -- A string containing the HTML code to be rendered. 
882   
883      'width' -- Column at which to word-wrap.  Default 72. 
884   
885      returns -- A string containing a plain text rendering of the 
886      HTML.""" 
887   
888      s = StringIO.StringIO() 
889      w = formatter.DumbWriter(s, width) 
890      f = formatter.AbstractFormatter(w) 
891      p = htmllib.HTMLParser(f) 
892      p.feed(html) 
893      p.close() 
894      return s.getvalue() 
 895   
896   
897   
898   
899   
900   
901  rc = RcConfiguration() 
902  """The configuration stored in system and user rc files.""" 
903   
904   
905  _unique_tag = 0 
906   
907   
908   
909   
910   
911   
912   
913