diff options
Diffstat (limited to 'third_party')
71 files changed, 18974 insertions, 0 deletions
diff --git a/third_party/Python/module/pexpect-2.4/ANSI.py b/third_party/Python/module/pexpect-2.4/ANSI.py new file mode 100644 index 000000000000..537017e90b29 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/ANSI.py @@ -0,0 +1,334 @@ +"""This implements an ANSI terminal emulator as a subclass of screen. + +$Id: ANSI.py 491 2007-12-16 20:04:57Z noah $ +""" +# references: +# http://www.retards.org/terminals/vt102.html +# http://vt100.net/docs/vt102-ug/contents.html +# http://vt100.net/docs/vt220-rm/ +# http://www.termsys.demon.co.uk/vtansi.htm + +import screen +import FSM +import copy +import string + +def Emit (fsm): + + screen = fsm.memory[0] + screen.write_ch(fsm.input_symbol) + +def StartNumber (fsm): + + fsm.memory.append (fsm.input_symbol) + +def BuildNumber (fsm): + + ns = fsm.memory.pop() + ns = ns + fsm.input_symbol + fsm.memory.append (ns) + +def DoBackOne (fsm): + + screen = fsm.memory[0] + screen.cursor_back () + +def DoBack (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_back (count) + +def DoDownOne (fsm): + + screen = fsm.memory[0] + screen.cursor_down () + +def DoDown (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_down (count) + +def DoForwardOne (fsm): + + screen = fsm.memory[0] + screen.cursor_forward () + +def DoForward (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_forward (count) + +def DoUpReverse (fsm): + + screen = fsm.memory[0] + screen.cursor_up_reverse() + +def DoUpOne (fsm): + + screen = fsm.memory[0] + screen.cursor_up () + +def DoUp (fsm): + + count = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_up (count) + +def DoHome (fsm): + + c = int(fsm.memory.pop()) + r = int(fsm.memory.pop()) + screen = fsm.memory[0] + screen.cursor_home (r,c) + +def DoHomeOrigin (fsm): + + c = 1 + r = 1 + screen = fsm.memory[0] + screen.cursor_home (r,c) + +def DoEraseDown (fsm): + + screen = fsm.memory[0] + screen.erase_down() + +def DoErase (fsm): + + arg = int(fsm.memory.pop()) + screen = fsm.memory[0] + if arg == 0: + screen.erase_down() + elif arg == 1: + screen.erase_up() + elif arg == 2: + screen.erase_screen() + +def DoEraseEndOfLine (fsm): + + screen = fsm.memory[0] + screen.erase_end_of_line() + +def DoEraseLine (fsm): + + screen = fsm.memory[0] + if arg == 0: + screen.end_of_line() + elif arg == 1: + screen.start_of_line() + elif arg == 2: + screen.erase_line() + +def DoEnableScroll (fsm): + + screen = fsm.memory[0] + screen.scroll_screen() + +def DoCursorSave (fsm): + + screen = fsm.memory[0] + screen.cursor_save_attrs() + +def DoCursorRestore (fsm): + + screen = fsm.memory[0] + screen.cursor_restore_attrs() + +def DoScrollRegion (fsm): + + screen = fsm.memory[0] + r2 = int(fsm.memory.pop()) + r1 = int(fsm.memory.pop()) + screen.scroll_screen_rows (r1,r2) + +def DoMode (fsm): + + screen = fsm.memory[0] + mode = fsm.memory.pop() # Should be 4 + # screen.setReplaceMode () + +def Log (fsm): + + screen = fsm.memory[0] + fsm.memory = [screen] + fout = open ('log', 'a') + fout.write (fsm.input_symbol + ',' + fsm.current_state + '\n') + fout.close() + +class term (screen.screen): + """This is a placeholder. + In theory I might want to add other terminal types. + """ + def __init__ (self, r=24, c=80): + screen.screen.__init__(self, r,c) + +class ANSI (term): + + """This class encapsulates a generic terminal. It filters a stream and + maintains the state of a screen object. """ + + def __init__ (self, r=24,c=80): + + term.__init__(self,r,c) + + #self.screen = screen (24,80) + self.state = FSM.FSM ('INIT',[self]) + self.state.set_default_transition (Log, 'INIT') + self.state.add_transition_any ('INIT', Emit, 'INIT') + self.state.add_transition ('\x1b', 'INIT', None, 'ESC') + self.state.add_transition_any ('ESC', Log, 'INIT') + self.state.add_transition ('(', 'ESC', None, 'G0SCS') + self.state.add_transition (')', 'ESC', None, 'G1SCS') + self.state.add_transition_list ('AB012', 'G0SCS', None, 'INIT') + self.state.add_transition_list ('AB012', 'G1SCS', None, 'INIT') + self.state.add_transition ('7', 'ESC', DoCursorSave, 'INIT') + self.state.add_transition ('8', 'ESC', DoCursorRestore, 'INIT') + self.state.add_transition ('M', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('>', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('<', 'ESC', DoUpReverse, 'INIT') + self.state.add_transition ('=', 'ESC', None, 'INIT') # Selects application keypad. + self.state.add_transition ('#', 'ESC', None, 'GRAPHICS_POUND') + self.state.add_transition_any ('GRAPHICS_POUND', None, 'INIT') + self.state.add_transition ('[', 'ESC', None, 'ELB') + # ELB means Escape Left Bracket. That is ^[[ + self.state.add_transition ('H', 'ELB', DoHomeOrigin, 'INIT') + self.state.add_transition ('D', 'ELB', DoBackOne, 'INIT') + self.state.add_transition ('B', 'ELB', DoDownOne, 'INIT') + self.state.add_transition ('C', 'ELB', DoForwardOne, 'INIT') + self.state.add_transition ('A', 'ELB', DoUpOne, 'INIT') + self.state.add_transition ('J', 'ELB', DoEraseDown, 'INIT') + self.state.add_transition ('K', 'ELB', DoEraseEndOfLine, 'INIT') + self.state.add_transition ('r', 'ELB', DoEnableScroll, 'INIT') + self.state.add_transition ('m', 'ELB', None, 'INIT') + self.state.add_transition ('?', 'ELB', None, 'MODECRAP') + self.state.add_transition_list (string.digits, 'ELB', StartNumber, 'NUMBER_1') + self.state.add_transition_list (string.digits, 'NUMBER_1', BuildNumber, 'NUMBER_1') + self.state.add_transition ('D', 'NUMBER_1', DoBack, 'INIT') + self.state.add_transition ('B', 'NUMBER_1', DoDown, 'INIT') + self.state.add_transition ('C', 'NUMBER_1', DoForward, 'INIT') + self.state.add_transition ('A', 'NUMBER_1', DoUp, 'INIT') + self.state.add_transition ('J', 'NUMBER_1', DoErase, 'INIT') + self.state.add_transition ('K', 'NUMBER_1', DoEraseLine, 'INIT') + self.state.add_transition ('l', 'NUMBER_1', DoMode, 'INIT') + ### It gets worse... the 'm' code can have infinite number of + ### number;number;number before it. I've never seen more than two, + ### but the specs say it's allowed. crap! + self.state.add_transition ('m', 'NUMBER_1', None, 'INIT') + ### LED control. Same problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_1', None, 'INIT') + + # \E[?47h appears to be "switch to alternate screen" + # \E[?47l restores alternate screen... I think. + self.state.add_transition_list (string.digits, 'MODECRAP', StartNumber, 'MODECRAP_NUM') + self.state.add_transition_list (string.digits, 'MODECRAP_NUM', BuildNumber, 'MODECRAP_NUM') + self.state.add_transition ('l', 'MODECRAP_NUM', None, 'INIT') + self.state.add_transition ('h', 'MODECRAP_NUM', None, 'INIT') + +#RM Reset Mode Esc [ Ps l none + self.state.add_transition (';', 'NUMBER_1', None, 'SEMICOLON') + self.state.add_transition_any ('SEMICOLON', Log, 'INIT') + self.state.add_transition_list (string.digits, 'SEMICOLON', StartNumber, 'NUMBER_2') + self.state.add_transition_list (string.digits, 'NUMBER_2', BuildNumber, 'NUMBER_2') + self.state.add_transition_any ('NUMBER_2', Log, 'INIT') + self.state.add_transition ('H', 'NUMBER_2', DoHome, 'INIT') + self.state.add_transition ('f', 'NUMBER_2', DoHome, 'INIT') + self.state.add_transition ('r', 'NUMBER_2', DoScrollRegion, 'INIT') + ### It gets worse... the 'm' code can have infinite number of + ### number;number;number before it. I've never seen more than two, + ### but the specs say it's allowed. crap! + self.state.add_transition ('m', 'NUMBER_2', None, 'INIT') + ### LED control. Same problem as 'm' code. + self.state.add_transition ('q', 'NUMBER_2', None, 'INIT') + + def process (self, c): + + self.state.process(c) + + def process_list (self, l): + + self.write(l) + + def write (self, s): + + for c in s: + self.process(c) + + def flush (self): + + pass + + def write_ch (self, ch): + + """This puts a character at the current cursor position. cursor + position if moved forward with wrap-around, but no scrolling is done if + the cursor hits the lower-right corner of the screen. """ + + #\r and \n both produce a call to crlf(). + ch = ch[0] + + if ch == '\r': + # self.crlf() + return + if ch == '\n': + self.crlf() + return + if ch == chr(screen.BS): + self.cursor_back() + self.put_abs(self.cur_r, self.cur_c, ' ') + return + + if ch not in string.printable: + fout = open ('log', 'a') + fout.write ('Nonprint: ' + str(ord(ch)) + '\n') + fout.close() + return + self.put_abs(self.cur_r, self.cur_c, ch) + old_r = self.cur_r + old_c = self.cur_c + self.cursor_forward() + if old_c == self.cur_c: + self.cursor_down() + if old_r != self.cur_r: + self.cursor_home (self.cur_r, 1) + else: + self.scroll_up () + self.cursor_home (self.cur_r, 1) + self.erase_line() + +# def test (self): +# +# import sys +# write_text = 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(He\'s got a ferret sticking up his nose.)\n' + \ +# 'How it got there I can\'t tell\n' + \ +# 'But now it\'s there it hurts like hell\n' + \ +# 'And what is more it radically affects my sense of smell.\n' + \ +# '(His sense of smell.)\n' + \ +# 'I can see a bare-bottomed mandril.\n' + \ +# '(Slyly eyeing his other nostril.)\n' + \ +# 'If it jumps inside there too I really don\'t know what to do\n' + \ +# 'I\'ll be the proud posessor of a kind of nasal zoo.\n' + \ +# '(A nasal zoo.)\n' + \ +# 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(And what is worst of all it constantly explodes.)\n' + \ +# '"Ferrets don\'t explode," you say\n' + \ +# 'But it happened nine times yesterday\n' + \ +# 'And I should know for each time I was standing in the way.\n' + \ +# 'I\'ve got a ferret sticking up my nose.\n' + \ +# '(He\'s got a ferret sticking up his nose.)\n' + \ +# 'How it got there I can\'t tell\n' + \ +# 'But now it\'s there it hurts like hell\n' + \ +# 'And what is more it radically affects my sense of smell.\n' + \ +# '(His sense of smell.)' +# self.fill('.') +# self.cursor_home() +# for c in write_text: +# self.write_ch (c) +# print str(self) +# +#if __name__ == '__main__': +# t = ANSI(6,65) +# t.test() diff --git a/third_party/Python/module/pexpect-2.4/FSM.py b/third_party/Python/module/pexpect-2.4/FSM.py new file mode 100644 index 000000000000..751eb37e1304 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/FSM.py @@ -0,0 +1,331 @@ +#!/usr/bin/env python + +"""This module implements a Finite State Machine (FSM). In addition to state +this FSM also maintains a user defined "memory". So this FSM can be used as a +Push-down Automata (PDA) since a PDA is a FSM + memory. + +The following describes how the FSM works, but you will probably also need to +see the example function to understand how the FSM is used in practice. + +You define an FSM by building tables of transitions. For a given input symbol +the process() method uses these tables to decide what action to call and what +the next state will be. The FSM has a table of transitions that associate: + + (input_symbol, current_state) --> (action, next_state) + +Where "action" is a function you define. The symbols and states can be any +objects. You use the add_transition() and add_transition_list() methods to add +to the transition table. The FSM also has a table of transitions that +associate: + + (current_state) --> (action, next_state) + +You use the add_transition_any() method to add to this transition table. The +FSM also has one default transition that is not associated with any specific +input_symbol or state. You use the set_default_transition() method to set the +default transition. + +When an action function is called it is passed a reference to the FSM. The +action function may then access attributes of the FSM such as input_symbol, +current_state, or "memory". The "memory" attribute can be any object that you +want to pass along to the action functions. It is not used by the FSM itself. +For parsing you would typically pass a list to be used as a stack. + +The processing sequence is as follows. The process() method is given an +input_symbol to process. The FSM will search the table of transitions that +associate: + + (input_symbol, current_state) --> (action, next_state) + +If the pair (input_symbol, current_state) is found then process() will call the +associated action function and then set the current state to the next_state. + +If the FSM cannot find a match for (input_symbol, current_state) it will then +search the table of transitions that associate: + + (current_state) --> (action, next_state) + +If the current_state is found then the process() method will call the +associated action function and then set the current state to the next_state. +Notice that this table lacks an input_symbol. It lets you define transitions +for a current_state and ANY input_symbol. Hence, it is called the "any" table. +Remember, it is always checked after first searching the table for a specific +(input_symbol, current_state). + +For the case where the FSM did not match either of the previous two cases the +FSM will try to use the default transition. If the default transition is +defined then the process() method will call the associated action function and +then set the current state to the next_state. This lets you define a default +transition as a catch-all case. You can think of it as an exception handler. +There can be only one default transition. + +Finally, if none of the previous cases are defined for an input_symbol and +current_state then the FSM will raise an exception. This may be desirable, but +you can always prevent this just by defining a default transition. + +Noah Spurrier 20020822 +""" + +class ExceptionFSM(Exception): + + """This is the FSM Exception class.""" + + def __init__(self, value): + self.value = value + + def __str__(self): + return `self.value` + +class FSM: + + """This is a Finite State Machine (FSM). + """ + + def __init__(self, initial_state, memory=None): + + """This creates the FSM. You set the initial state here. The "memory" + attribute is any object that you want to pass along to the action + functions. It is not used by the FSM. For parsing you would typically + pass a list to be used as a stack. """ + + # Map (input_symbol, current_state) --> (action, next_state). + self.state_transitions = {} + # Map (current_state) --> (action, next_state). + self.state_transitions_any = {} + self.default_transition = None + + self.input_symbol = None + self.initial_state = initial_state + self.current_state = self.initial_state + self.next_state = None + self.action = None + self.memory = memory + + def reset (self): + + """This sets the current_state to the initial_state and sets + input_symbol to None. The initial state was set by the constructor + __init__(). """ + + self.current_state = self.initial_state + self.input_symbol = None + + def add_transition (self, input_symbol, state, action=None, next_state=None): + + """This adds a transition that associates: + + (input_symbol, current_state) --> (action, next_state) + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. + + You can also set transitions for a list of symbols by using + add_transition_list(). """ + + if next_state is None: + next_state = state + self.state_transitions[(input_symbol, state)] = (action, next_state) + + def add_transition_list (self, list_input_symbols, state, action=None, next_state=None): + + """This adds the same transition for a list of input symbols. + You can pass a list or a string. Note that it is handy to use + string.digits, string.whitespace, string.letters, etc. to add + transitions that match character classes. + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. """ + + if next_state is None: + next_state = state + for input_symbol in list_input_symbols: + self.add_transition (input_symbol, state, action, next_state) + + def add_transition_any (self, state, action=None, next_state=None): + + """This adds a transition that associates: + + (current_state) --> (action, next_state) + + That is, any input symbol will match the current state. + The process() method checks the "any" state associations after it first + checks for an exact match of (input_symbol, current_state). + + The action may be set to None in which case the process() method will + ignore the action and only set the next_state. The next_state may be + set to None in which case the current state will be unchanged. """ + + if next_state is None: + next_state = state + self.state_transitions_any [state] = (action, next_state) + + def set_default_transition (self, action, next_state): + + """This sets the default transition. This defines an action and + next_state if the FSM cannot find the input symbol and the current + state in the transition list and if the FSM cannot find the + current_state in the transition_any list. This is useful as a final + fall-through state for catching errors and undefined states. + + The default transition can be removed by setting the attribute + default_transition to None. """ + + self.default_transition = (action, next_state) + + def get_transition (self, input_symbol, state): + + """This returns (action, next state) given an input_symbol and state. + This does not modify the FSM state, so calling this method has no side + effects. Normally you do not call this method directly. It is called by + process(). + + The sequence of steps to check for a defined transition goes from the + most specific to the least specific. + + 1. Check state_transitions[] that match exactly the tuple, + (input_symbol, state) + + 2. Check state_transitions_any[] that match (state) + In other words, match a specific state and ANY input_symbol. + + 3. Check if the default_transition is defined. + This catches any input_symbol and any state. + This is a handler for errors, undefined states, or defaults. + + 4. No transition was defined. If we get here then raise an exception. + """ + + if self.state_transitions.has_key((input_symbol, state)): + return self.state_transitions[(input_symbol, state)] + elif self.state_transitions_any.has_key (state): + return self.state_transitions_any[state] + elif self.default_transition is not None: + return self.default_transition + else: + raise ExceptionFSM ('Transition is undefined: (%s, %s).' % + (str(input_symbol), str(state)) ) + + def process (self, input_symbol): + + """This is the main method that you call to process input. This may + cause the FSM to change state and call an action. This method calls + get_transition() to find the action and next_state associated with the + input_symbol and current_state. If the action is None then the action + is not called and only the current state is changed. This method + processes one complete input symbol. You can process a list of symbols + (or a string) by calling process_list(). """ + + self.input_symbol = input_symbol + (self.action, self.next_state) = self.get_transition (self.input_symbol, self.current_state) + if self.action is not None: + self.action (self) + self.current_state = self.next_state + self.next_state = None + + def process_list (self, input_symbols): + + """This takes a list and sends each element to process(). The list may + be a string or any iterable object. """ + + for s in input_symbols: + self.process (s) + +############################################################################## +# The following is an example that demonstrates the use of the FSM class to +# process an RPN expression. Run this module from the command line. You will +# get a prompt > for input. Enter an RPN Expression. Numbers may be integers. +# Operators are * / + - Use the = sign to evaluate and print the expression. +# For example: +# +# 167 3 2 2 * * * 1 - = +# +# will print: +# +# 2003 +############################################################################## + +import sys, os, traceback, optparse, time, string + +# +# These define the actions. +# Note that "memory" is a list being used as a stack. +# + +def BeginBuildNumber (fsm): + fsm.memory.append (fsm.input_symbol) + +def BuildNumber (fsm): + s = fsm.memory.pop () + s = s + fsm.input_symbol + fsm.memory.append (s) + +def EndBuildNumber (fsm): + s = fsm.memory.pop () + fsm.memory.append (int(s)) + +def DoOperator (fsm): + ar = fsm.memory.pop() + al = fsm.memory.pop() + if fsm.input_symbol == '+': + fsm.memory.append (al + ar) + elif fsm.input_symbol == '-': + fsm.memory.append (al - ar) + elif fsm.input_symbol == '*': + fsm.memory.append (al * ar) + elif fsm.input_symbol == '/': + fsm.memory.append (al / ar) + +def DoEqual (fsm): + print str(fsm.memory.pop()) + +def Error (fsm): + print 'That does not compute.' + print str(fsm.input_symbol) + +def main(): + + """This is where the example starts and the FSM state transitions are + defined. Note that states are strings (such as 'INIT'). This is not + necessary, but it makes the example easier to read. """ + + f = FSM ('INIT', []) # "memory" will be used as a stack. + f.set_default_transition (Error, 'INIT') + f.add_transition_any ('INIT', None, 'INIT') + f.add_transition ('=', 'INIT', DoEqual, 'INIT') + f.add_transition_list (string.digits, 'INIT', BeginBuildNumber, 'BUILDING_NUMBER') + f.add_transition_list (string.digits, 'BUILDING_NUMBER', BuildNumber, 'BUILDING_NUMBER') + f.add_transition_list (string.whitespace, 'BUILDING_NUMBER', EndBuildNumber, 'INIT') + f.add_transition_list ('+-*/', 'INIT', DoOperator, 'INIT') + + print + print 'Enter an RPN Expression.' + print 'Numbers may be integers. Operators are * / + -' + print 'Use the = sign to evaluate and print the expression.' + print 'For example: ' + print ' 167 3 2 2 * * * 1 - =' + inputstr = raw_input ('> ') + f.process_list(inputstr) + +if __name__ == '__main__': + try: + start_time = time.time() + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: FSM.py 490 2007-12-07 15:46:24Z noah $') + parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output') + (options, args) = parser.parse_args() + if options.verbose: print time.asctime() + main() + if options.verbose: print time.asctime() + if options.verbose: print 'TOTAL TIME IN MINUTES:', + if options.verbose: print (time.time() - start_time) / 60.0 + sys.exit(0) + except KeyboardInterrupt, e: # Ctrl-C + raise e + except SystemExit, e: # sys.exit() + raise e + except Exception, e: + print 'ERROR, UNEXPECTED EXCEPTION' + print str(e) + traceback.print_exc() + os._exit(1) diff --git a/third_party/Python/module/pexpect-2.4/INSTALL b/third_party/Python/module/pexpect-2.4/INSTALL new file mode 100644 index 000000000000..509e925f9491 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/INSTALL @@ -0,0 +1,31 @@ +Installation +------------ +This is a standard Python Distutil distribution. To install simply run: + + python setup.py install + +This makes Pexpect available to any script on the machine. You need +root access to install it this way. If you do not have root access or +if you do not wish to install Pexpect so that is available to any script +then you can just copy the pexpect.py file to same directory as your script. + +Trouble on Debian and Ubuntu +---------------------------- +For some stupid reason Debian Linux does not include the distutils module +in the standard 'python' package. Instead, the distutils module is packaged +separately in the 'python-dev' package. So to add distutils back +into Python, simply use aptitude or apt-get to install 'python-dev'. +As root, run this command: + apt-get install python-dev +Why they do this is mysterious because: + - It breaks the Python model of "batteries included". + 'distutils' isn't an extra or optional module -- + it's parts of the Standard Python Library. + - The Debian 'python-dev' package is a microscopic 50K installed. + So what are they saving? + - Distutils is not only interesting to developers. Many non-development + oriented Python packages use 'distutils' to install applications. + - As far as I can tell, the package maintainers must go through + more trouble to remove 'distutils' from the standard Python + distribution than it would take just to leave it in. + diff --git a/third_party/Python/module/pexpect-2.4/LICENSE b/third_party/Python/module/pexpect-2.4/LICENSE new file mode 100644 index 000000000000..e61144381c73 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/LICENSE @@ -0,0 +1,21 @@ +Free, open source, and all that good stuff. +Pexpect Copyright (c) 2008 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/third_party/Python/module/pexpect-2.4/PKG-INFO b/third_party/Python/module/pexpect-2.4/PKG-INFO new file mode 100644 index 000000000000..71cb54999eef --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/PKG-INFO @@ -0,0 +1,10 @@ +Metadata-Version: 1.0 +Name: pexpect +Version: 2.4 +Summary: Pexpect is a pure Python Expect. It allows easy control of other applications. +Home-page: http://pexpect.sourceforge.net/ +Author: Noah Spurrier +Author-email: noah@noah.org +License: MIT license +Description: UNKNOWN +Platform: UNIX diff --git a/third_party/Python/module/pexpect-2.4/README b/third_party/Python/module/pexpect-2.4/README new file mode 100644 index 000000000000..3101dc890167 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/README @@ -0,0 +1,45 @@ +Pexpect is a Pure Python Expect-like module + +Pexpect makes Python a better tool for controlling other applications. + +Pexpect is a pure Python module for spawning child applications; controlling +them; and responding to expected patterns in their output. Pexpect works like +Don Libes' Expect. Pexpect allows your script to spawn a child application and +control it as if a human were typing commands. + +Pexpect can be used for automating interactive applications such as ssh, ftp, +passwd, telnet, etc. It can be used to a automate setup scripts for +duplicating software package installations on different servers. It can be +used for automated software testing. Pexpect is in the spirit of Don Libes' +Expect, but Pexpect is pure Python. Unlike other Expect-like modules for +Python, Pexpect does not require TCL or Expect nor does it require C +extensions to be compiled. It should work on any platform that supports the +standard Python pty module. The Pexpect interface was designed to be easy to use. + +If you want to work with the development version of the source code then please +read the DEVELOPERS document in the root of the source code tree. + +Free, open source, and all that good stuff. +Pexpect Copyright (c) 2008 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +Noah Spurrier +http://pexpect.sourceforge.net/ + diff --git a/third_party/Python/module/pexpect-2.4/doc/clean.css b/third_party/Python/module/pexpect-2.4/doc/clean.css new file mode 100644 index 000000000000..e8d98ddb2e59 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/doc/clean.css @@ -0,0 +1,103 @@ + +body { + margin:0px; + padding:0px; + font-family:verdana, arial, helvetica, sans-serif; + color:#333; + background-color:white; + } +pre { + background: #eeeeee; + border: 1px solid #888888; + color: black; + padding: 1em; + white-space: pre; +} +h1 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:20px; + line-height:28px; + font-weight:900; + color:#44f; + } +h2 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:17px; + line-height:28px; + font-weight:900; + color:#226; + } +h3 { + margin:5px 0px 5px 0px; + padding:0px; + font-size:15px; + line-height:28px; + font-weight:900; + } +p +{ + margin:0px 0px 16px 0px; + font:11px/20px verdana, arial, helvetica, sans-serif; + padding:0px; +} +table +{ + font-size: 10pt; + color: #000000; +} +td{border:1px solid #999;} + +table.pymenu {color: #000000; background-color: #99ccff} +th.pymenu {color: #ffffff; background-color: #003366} + +.code +{ + font-family: "Lucida Console", monospace; font-weight: bold; + color: #007700; background-color: #eeeeee +} + +#Content>p {margin:0px;} +#Content>p+p {text-indent:30px;} + +a { + text-decoration:none; + font-weight:600; + font-family:verdana, arial, helvetica, sans-serif; + color: #900; +} +//a:link {color:#09c;} +//a x:visited {color:#07a;} +a:hover {background-color:#ee0;} + +#Header { + margin:10px 0px 10px 0px; + padding:10px 0px 10px 20px; + /* For IE5/Win's benefit height = [correct height] + [top padding] + [top and bottom border widths] */ + height:33px; /* 14px + 17px + 2px = 33px */ + border-style:solid; + border-color:black; + border-width:1px 0px; /* top and bottom borders: 1px; left and right borders: 0px */ + line-height:33px; + background-color:#eee; + height:66px; /* the correct height */ + } + +#Content { + margin:0px 210px 50px 10px; + padding:10px; + } + +#Menu { + position:absolute; + top:100px; + right:20px; + width:172px; + padding:10px; + background-color:#eee; + border:1px solid #999; // dashed #999; + line-height:17px; + width:150px; + font-size:11px; + } diff --git a/third_party/Python/module/pexpect-2.4/doc/email.png b/third_party/Python/module/pexpect-2.4/doc/email.png Binary files differnew file mode 100644 index 000000000000..511113815b18 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/doc/email.png diff --git a/third_party/Python/module/pexpect-2.4/doc/examples.html b/third_party/Python/module/pexpect-2.4/doc/examples.html new file mode 100644 index 000000000000..2884a5c6cdc4 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/doc/examples.html @@ -0,0 +1,135 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<title>Pexpect - Examples</title> +<link rel="stylesheet" href="clean.css" type="text/css"> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<meta name="Author" content="Noah Spurrier"> +<meta name="Keywords" + content="pexpect, Noah Spurrier, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like"> +<meta name="Description" content="Examples for using Pexpect."> +</head> +<body bgcolor="#ffffff" text="#000000"> +<div id="Header"> +<h1>Pexpect Examples</h1> +</div> +<div id="Content"> + +<p><span class="code">hive.py</span></p> +<p><blockquote> +This script creates SSH connections to a list of hosts that +you provide. Then you are given a command line prompt. Each +shell command that you enter is sent to all the hosts. The +response from each host is collected and printed. For example, +you could connect to a dozen different machines and reboot +them all at once. +</p></blockquote> + +<p><span class="code">script.py</span></p> +<p><blockquote> + This implements a command similar to the classic BSD +"script" command. + This will start a subshell and log all input and +output to a file. + This demonstrates the interact() method of Pexpect. +</p></blockquote> + +<p><span class="code">fix_cvs_files.py</span></p> +<p><blockquote> + This is for cleaning up binary files improperly +added to CVS. + This script scans the given path to find binary +files; + checks with CVS to see if the sticky options are set +to -kb; + finally if sticky options are not -kb then uses 'cvs +admin' to + set the -kb option. +</p></blockquote> + +<p><span class="code">ftp.py</span></p> +<p><blockquote> + This demonstrates an FTP "bookmark". + This connects to an ftp site; does a few ftp stuff; +and then gives the user + interactive control over the session. In this case +the "bookmark" is to a + directory on the OpenBSD ftp server. It puts you in +the i386 packages + directory. You can easily modify this for other +sites. + This demonstrates the interact() method of Pexpect. +</p></blockquote> + +<p><span class="code">monitor.py</span></p> +<p><blockquote> + This runs a sequence of commands on a remote host +using SSH. + It runs a simple system checks such as uptime and +free to monitor + the state of the remote host. +</p></blockquote> + +<p><span class="code">passmass.py</span></p> +<p><blockquote> + This will login to each given server and change the +password of the + given user. This demonstrates scripting logins and +passwords. +</p></blockquote> + +<p><span class="code">python.py</span></p> +<p><blockquote> + This starts the python interpreter and prints the +greeting message backwards. + It then gives the user iteractive control of Python. +It's pretty useless! +</p></blockquote> + +<p><span class="code">rippy.py</span></p> +<p><blockquote> + This is a wizard for mencoder. It greatly simplifies +the process of + ripping a DVD to Divx (mpeg4) format. It can +transcode from any + video file to another. It has options for resampling +the audio stream; + removing interlace artifacts, fitting to a target +file size, etc. + There are lots of options, but the process is simple +and easy to use. +</p></blockquote> + +<p><span class="code">sshls.py</span></p> +<p><blockquote> + This lists a directory on a remote machine. +</p></blockquote> +<p><span class="code">ssh_tunnel.py</span></p> +<p><blockquote> + This starts an SSH tunnel to a remote machine. It +monitors the connection + and restarts the tunnel if it goes down. +</p></blockquote> +<p><span class="code">uptime.py</span></p> +<p><blockquote> + This will run the uptime command and parse the +output into variables. + This demonstrates using a single regular expression +to match the output + of a command and capturing different variable in +match groups. + The grouping regular expression handles a wide variety of different +uptime formats. + </blockquote> + +<p> +<a href="http://sourceforge.net/projects/pexpect/" + title="The Pexpect project page on SourceForge.net"> <img + src="http://sourceforge.net/sflogo.php?group_id=59762&type=5" + alt="The Pexpect project page on SourceForge.net" border="0" + height="31" width="105"> </a> +</p> +</div> + +</body> +</html> diff --git a/third_party/Python/module/pexpect-2.4/doc/index.html b/third_party/Python/module/pexpect-2.4/doc/index.html new file mode 100644 index 000000000000..725fe984ce66 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/doc/index.html @@ -0,0 +1,868 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<title>Pexpect - a Pure Python Expect-like module</title> +<link rel="stylesheet" href="clean.css" type="text/css"> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<meta name="Author" content="Noah Spurrier"> +<meta name="Keywords" + content="pexpect, Noah Spurrier, pypect, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like"> +<meta name="Description" + content="Pexpect is a pure Python Expect-like module. Pexpect makes Python a better tool for controlling other applications."> +</head> +<body bgcolor="#ffffff" text="#000000"> +<div id="Header"> +<h1>Pexpect version 2.4<br> +a Pure Python Expect-like module +</h1> +</div> +<div id="Content"> +<p>Pexpect makes Python a better tool for controlling other +applications.</p> +<p>Pexpect is a pure Python module for spawning child applications; +controlling them; and responding to expected patterns in their output. +Pexpect works like Don Libes' Expect. Pexpect allows your script to +spawn a child application and control it as if a human were typing +commands.</p> +<p>Pexpect can be used for automating interactive applications such as +ssh, ftp, passwd, telnet, etc. It can be used to a automate setup +scripts for duplicating software package installations on different +servers. It can be used for automated software testing. Pexpect is in +the spirit of Don Libes' Expect, but Pexpect is pure Python. Unlike +other Expect-like modules for Python, Pexpect does not require TCL or +Expect nor does it require C extensions to be compiled. It should work +on any platform that supports the standard Python pty module. The +Pexpect interface was designed to be easy to use.</p> +<table border="0"> + <tbody> + <tr> + <td align="right" valign="top">Send questions to:</td> + <td align="left"><a href="http://www.noah.org/email/"><img + src="email.png" alt="Click to send email." border="0" height="16" + width="100"></a></td> + </tr> + </tbody> +</table> +<hr noshade="noshade" size="1"> +<h1><a name="license"></a>License: MIT style</h1> +<p> +Free, open source, and all that good stuff.<br> +<br> +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:<br> +<br> +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.<br> +<br> +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.<br> +<br> +Pexpect Copyright (c) 2008 Noah Spurrier<br> +http://pexpect.sourceforge.net/ +</p> + +<hr noshade="noshade" size="1"> +<h1><a name="download"></a><a + href="http://sourceforge.net/project/showfiles.php?group_id=59762">Download</a></h1> +<p>Download the <a + href="http://sourceforge.net/project/showfiles.php?group_id=59762"> +current version here</a> from the SourceForge site. Grab the current Pexpect tarball. +</p> +<h2>Installing Pexpect</h2> +<p>The Pexpect tarball is a standard Python Distutil distribution.</p> +<ol> + <li>download <span class="code">pexpect-2.4.tar.gz</span></li> + <li><span class="code">tar zxf pexpect-2.4.tar.gz</span></li> + <li><span class="code">cd pexpect-2.4</span></li> + <li><span class="code">python setup.py install</span> <i>do this as root</i></li> +</ol> +<h2>Examples</h2> +<p> +Under the <span class="code">pexpect-2.4</span> directory you should find +the <span class="code">examples</span> directory. +This is the best way to learn to use Pexpect. +See the descriptions of <a href="examples.html">Pexpect Examples</a>. +</p> +<h2><a name="doc"></a>API Documentation</h2> +<p> +<blockquote> +<a href="pexpect.html">pexpect</a> This is the main module that you want.<br> +<a href="pxssh.html">pxssh</a> Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.<br> +</blockquote> +the following are experimental extensions to Pexpect<br> +<blockquote> +<a href="fdpexpect.html">fdpexpect</a> fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.<br> +<a href="screen.html">SCREEN</a> This represents a virtual 'screen'.<br> +<a href="ANSI.html">ANSI</a> This parses ANSI/VT-100 terminal escape codes.<br> +<a href="FSM.html">FSM</a> This is a finite state machine used by ANSI.<br> +</blockquote> +</p> +<hr noshade="noshade" size="1"> +<h1><a name="status"></a>Project Status</h1> +<p>Automated pyunit tests reach over 80% +code coverage on pexpect.py. I regularly test on Linux and BSD +platforms. I try to test on Solaris and Irix. +</p> +<hr noshade="noshade" size="1"> +<h1><a name="requirements"></a>Requirements for use of Pexpect</h1> +<h2>Python</h2> +<blockquote> + <p>Pexpect was written and tested with Python 2.4. It should work on +earlier versions that have the <span class="code">pty</span> module. I +sometimes even manually test it with Python 1.5.2, but I can't easily +run the PyUnit test framework against Python 1.5.2, so I have less +confidence in Pexpect on Python 1.5.2.</p> +</blockquote> +<h2>pty module</h2> +<blockquote> + <p>Any POSIX system (UNIX) with a working <span class="code">pty</span> +module should be able to run Pexpect. The <span class="code">pty</span> +module is part of the Standard Python Library, so if you are running on +a POSIX system you should have it. The <span class="code">pty</span> +module does not run the same on all platforms. It should be solid on Linux +and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more +about the wrinkles see <a href="#bugs">Bugs</a> and <a href="#testing">Testing</a>.</p> +</blockquote> +<p>Pexpect does not currently work on the standard Windows Python (see +the pty requirement); however, it seems to work fine using <a + href="http://www.cygwin.com/">Cygwin</a>. It is possible to build +something like a pty for Windows, but it would have to use a different +technique that I am still investigating. I know it's possible because +Libes' Expect was ported to Windows. <i>If you have any ideas or +skills to contribute in this area then I would really appreciate some +tips on how to approach this problem.</i> </p> +<hr noshade="noshade" size="1"> +<h1><a name="overview"></a>Overview</h1> +<p>Pexpect can be used for automating interactive applications such as +ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be +easy to use. Here is an example of Pexpect in action:</p> +<blockquote> + <pre class="code"># This connects to the openbsd ftp site and<br># downloads the recursive directory listing.<br>import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('cd pub')<br>child.expect('ftp> ')<br>child.sendline ('get ls-lR.gz')<br>child.expect('ftp> ')<br>child.sendline ('bye')<br></pre> +</blockquote> +<p> Obviously you could write an ftp client using Python's own <span + class="code">ftplib</span> module, but this is just a demonstration. +You can use this technique with any application. This is especially +handy if you are writing automated test tools.</p> + +<p>There are two important methods in Pexpect -- <span class="code"><b>expect()</b></span> +and <span class="code"><b>send()</b></span> (or <span class="code">sendline()</span> +which is like <span class="code">send()</span> with a linefeed). +The <span class="code">expect()</span> method waits for the child application +to return a given string. The string you specify is a regular expression, so +you can match complicated patterns. The <span class="code"><b>send()</b></span> method +writes a string to the child application. From the child's point of +view it looks just like someone typed the text from a terminal. After +each call to <span class="code"><b>expect()</b></span> the <span + class="code"><b>before</b></span> and <span class="code"><b>after</b></span> +properties will be set to the text printed by child application. The <span + class="code"><b>before</b></span> property will contain all text up to +the expected string pattern. The <span class="code"><b>after</b></span> string +will contain the text that was matched by the expected pattern. +The <span class="code">match</span> property is set to the <span class="code">re MatchObject</span>. +</p> + +<p>An example of Pexpect in action may make things more clear. This example uses +<span class="code">ftp</span> to login to the OpenBSD site; list files +in a directory; and then pass interactive control of the ftp session to +the human user.</p> +<blockquote> + <pre class="code">import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('ls /pub/OpenBSD/')<br>child.expect ('ftp> ')<br>print child.before # Print the result of the ls command.<br>child.interact() # Give control of the child to the user.<br></pre> +</blockquote> +<h2>Special EOF and TIMEOUT patterns</h2> +<p> +There are two special patterns to match the End Of File or a Timeout condition. +You you can pass these patterns to <span class="code">expect()</span>. +These patterns are not regular expressions. Use them like predefined constants. +</p> +<p>If the child has died and you have read all the child's output then ordinarily +<span class="code">expect()</span> will raise an <span class="code">EOF</span> +exception. You can read everything up to the EOF without generating an +exception by using the EOF pattern <span class="code">expect(pexpect.EOF)</span>. +In this case everything the child has output will be available in the <span + class="code">before</span> property.</p> +<p>The pattern given to <span class="code">expect()</span> may be a +regular expression or it may also be a <b>list</b> of regular expressions. +This allows you to match multiple optional responses. The <span class="code">expect()</span> +method returns the index of the pattern that was matched. For example, +say you wanted to login to a server. After entering a password you +could get various responses from the server -- your password could be +rejected; or you could be allowed in and asked for your terminal type; +or you could be let right in and given a command prompt. The following +code fragment gives an example of this:</p> +<blockquote> + <pre class="code">child.expect('password:')<br>child.sendline (my_secret_password)<br># We expect any of these three patterns...<br>i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])<br>if i==0:<br> print 'Permission denied on host. Can't login'<br> child.kill(0)<br>elif i==2:<br> print 'Login OK... need to send terminal type.'<br> child.sendline('vt100')<br> child.expect ('[#\$] ')<br>elif i==3:<br> print 'Login OK.'<br> print 'Shell command prompt', child.after</pre> +</blockquote> +<p>If nothing matches an expected pattern then expect will eventually +raise a TIMEOUT exception. The default time is 30 seconds, but you can +change this by passing a timeout argument to expect():</p> +<blockquote> + <pre class="code"># Wait no more than 2 minutes (120 seconds) for password prompt.<br>child.expect('password:', timeout=120)</pre> +</blockquote> +<h2>Find the end of line -- CR/LF conventions<br> +Matching at the end of a line can be tricky<br> +$ regex pattern is useless.<br> +</h2> +<p>Pexpect matches regular expressions a little differently than what +you might be used to. +</p> +<p><i><b>The $ pattern for end of line match is useless</b></i>. +The $ matches the end of string, but Pexpect reads from the child +one character at a time, so each character looks like the end of a line. +Pexpect can't do a look-ahead into the child's output stream. +In general you would have this situation when using regular expressions +with any stream.<br> +<i>Note, pexpect does have an internal buffer, so reads are faster +than one character at a time, but from the user's perspective the regex +patterns test happens one character at a time.</i></p> +<p>The best way to match the end of a line is to look for the +newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style. +It may surprise some UNIX people to learn that terminal TTY device drivers +(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify +the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so +when the child app prints "\n" you actually see "\r\n". +</p> +<p><b>UNIX uses just linefeeds to end lines of text, but not when it +comes to TTY devices!</b> TTY devices are more like the Windows world. +Each line of text end with a CR/LF combination. When you intercept data +from a UNIX command from a TTY device you will find that the TTY device +outputs a CR/LF combination. A UNIX command may only write a linefeed +(\n), but the TTY device driver converts it to CR/LF. This means that +your terminal will see lines end with CR/LF (hex <span class="code">0D 0A</span>). +Since Pexpect emulates a terminal, to match ends of lines you have to +expect the CR/LF combination.</p> +<blockquote> + <p class="code">child.expect ('\r\n')</p> +</blockquote> +<p>If you just need to skip past a new line then <span class="code">expect +('\n')</span> by itself will work, but if you are expecting a specific +pattern before the end of line then you need to explicitly look for the +\r. For example the following expects a word at the end of a line:</p> +<blockquote> + <p class="code">child.expect ('\w+\r\n')</p> +</blockquote> +<p>But the following would both fail:</p> +<blockquote> + <p class="code">child.expect ('\w+\n')</p> +</blockquote> +<p>And as explained before, trying to use '$' to match the end of line +would not work either:</p> +<blockquote> + <p class="code">child.expect ('\w+$')</p> +</blockquote> +<p>So if you need to explicitly look for the END OF LINE, you want to +look for the CR/LF combination -- not just the LF and not the $ pattern.</p> +<p>This problem is not limited to Pexpect. This problem happens any +time you try to perform a regular expression match on a stream. Regular +expressions need to look ahead. With a stream it is hard to look ahead +because the process generating the stream may not be finished. There is no +way to know if the process has paused momentarily or is finished and +waiting for you. <font color="#cc0000">Pexpect must implicitly always +do a NON greedy match (minimal) at the end of a input {### already said +this}.</font> </p> +<p>Pexpect compiles all regular expressions with the DOTALL flag. With +the DOTALL flag a "." will match a newline. See the Python <a + href="http://www.python.org/doc/current/lib/node115.html#l2h-733">documentation</a></p> +<h2>Beware of + and * at the end of input.</h2> +<p>Remember that any time you try to match a pattern that needs +look-ahead that you will always get a minimal match (non greedy). For +example, the following will always return just one character:</p> +<blockquote> + <p class="code">child.expect ('.+')</p> +</blockquote> +<p>This example will match successfully, but will always return no +characters:</p> +<blockquote> + <p class="code">child.expect ('.*')</p> +</blockquote> +<p>Generally any star * expression will match as little as possible</p> +<p>One thing you can do is to try to force a non-ambiguous character at +the end of your <span class="code">\d+</span> pattern. Expect that +character to delimit the string. For example, you might try making the +end of your pattrn be <span class="code">\D+</span> instead of <span + class="code">\D*</span>. That means number digits alone would not +satisfy the (<span class="code">\d+</span>) pattern. You would need +some number(s) and at least one <span class="code">\D</span> at the +end. </p> +<h2>Matching groups</h2> +<p>You can group regular expression using parenthesis. After a match, +the <span class="code">match</span> parameter of the spawn object will +contain the Python Match object. </p> +<h2>Examples</h2> +<p>Using "match" and groups...</p> +<h2>Debugging</h2> +<p>If you get the string value of a pexpect.spawn object you will get +lots of useful debugging information. For debugging it's very useful to +use the following pattern:</p> +<p>try:<br> + i = child.expect ([pattern1, pattern2, pattern3, +etc])<br> +except:<br> + print "Exception was thrown"<br> + print "debug information:"<br> + print str(child)<br> +</p> +<p>It is also useful to log the child's input and out to a file or the +screen. The following will turn on logging and send output to stdout +(the screen).<br> +</p> +<p> child = pexpect.spawn (foo)<br> + child.logfile = sys.stdout<br> +<br> +</p> +<hr noshade="noshade" size="1"> +<h1>Exceptions</h1> +<p><b>EOF</b></p> +<p>Note that two flavors of EOF Exception may be thrown. They are +virtually identical except for the message string. For practical +purposes you should have no need to distinguish between them, but they +do give a little extra information about what type of platform you are +running. The two messages are:</p> +<blockquote> + <p class="code">End Of File (EOF) in read(). Exception style platform.</p> + <p class="code">End Of File (EOF) in read(). Empty string style +platform.</p> +</blockquote> +<p>Some UNIX platforms will throw an exception when you try to read +from a file descriptor in the EOF state. Other UNIX platforms instead +quietly return an empty string to indicate that the EOF state has been +reached.</p> +<p><b>Expecting EOF</b></p> +<p>If you wish to read up to the end of the child's output without +generating an <span class="code">EOF</span> exception then use the <span + class="code">expect(pexpect.EOF)</span> method.</p> +<p><b>TIMEOUT</b></p> +<p>The <span class="code">expect()</span> and <span class="code">read()</span> +methods will also timeout if the child does not generate any output for +a given amount of time. If this happens they will raise a <span + class="code">TIMEOUT</span> exception. You can have these method +ignore a timeout and block indefinitely by passing None for the timeout +parameter.</p> +<blockquote> + <p class="code">child.expect(pexpect.EOF, timeout=None)</p> +</blockquote> +<hr noshade="noshade" size="1"> +<h1><a name="faq"></a>FAQ</h1> +<p><b>Q: Why don't shell pipe and redirect (| and >) work when I +spawn a command?</b></p> +<p> + +A: Remember that Pexpect does NOT interpret shell meta characters such as +redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the +command you are spawning. This is a common mistake. If you want to run a +command and pipe it through another command then you must also start a shell. +For example: + +<pre> + child = pexpect.spawn('/bin/sh -c "ls -l | grep LOG > log_list.txt"') + child.expect(pexpect.EOF) +</pre> + +The second form of spawn (where you pass a list of arguments) is useful in +situations where you wish to spawn a command and pass it its own argument list. +This can make syntax more clear. For example, the following is equivalent to +the previous example: + +<pre> + shell_cmd = 'ls -l | grep LOG > log_list.txt' + child = pexpect.spawn ('/bin/sh', ['-c', shell_cmd]) + child.expect (pexpect.EOF) +</pre> + +</p> +<p><b>Q: Isn't there already a Python Expect?</b></p> +<p>A: Yes, there are several of them. They usually require you to +compile C. I wanted something that was pure Python and preferably a +single module that was simple to install. I also wanted something that +was easy to use. This pure Python expect only recently became possible +with the introduction of the pty module in the standard Python library. +Previously C extensions were required.</p> + +<p><strong>Q: The before and after properties sound weird.</strong></p> +<p>Originally I was going to model Pexpect more after Expect, but then +I found that I could never remember how to get the context of the stuff +I was trying to parse. I hate having to read my own documentation. I +decided that it was easier for me to remember what before and after +was. It just so happens that this is how the -B and -A options in grep +works, so that made it even easier for me to remember. Whatever makes +my life easier is what's best.</p> + +<p><b>Q: Why not just use Expect?</b></p> +<p>A: I love it. It's great. I has bailed me out of some real jams, but +I wanted something that would do 90% of what I need from Expect; be 10% +of the size; and allow me to write my code in Python instead of TCL. +Pexpect is not nearly as big as Expect, but Pexpect does everything I +have ever used Expect for. +<!-- :-P If I liked TCL then you wouldn't be reading this. My appologies to Don Libes -- Expect is cool, TK is cool, but TCL is only slightly better than Perl in my book. Hopefully after Expyct is done I will not need to use Expect anymore -- except for that lovely autoexpect tool. Damn, I wish I had that! --> </p> + +<p><b>Q: Why not just use a pipe (popen())?</b></p> +<p>A: A pipe works fine for getting the output to non-interactive +programs. If you just want to get the output from <span class="code">ls</span>, +<span class="code">uname</span>, or <span class="code">ping</span> +then this works. Pipes do not work very well for interactive programs +and pipes will almost certainly fail for most applications that ask for +passwords such as telnet, ftp, or ssh.</p> +<p>There are two reasons for this. </p> +<p>First an application may bypass stdout and print directly to its +controlling TTY. Something like SSH will do this when it asks you for a +password. This is why you cannot redirect the password prompt because +it does not go through stdout or stderr.</p> +<p>The second reason is because most applications are built using the C +Standard IO Library (anything that uses <span class="code">#include +<stdio.h></span>). One of the features of the stdio library is +that it buffers all input and output. Normally output is <b><i>line +buffered</i></b> when a program is printing to a TTY (your terminal +screen). Every time the program prints a line-feed the currently +buffered data will get printed to your screen. The problem comes when +you connect a pipe. The stdio library is smart and can tell that it is +printing to a pipe instead of a TTY. In that case it switches from line +buffer mode to <i><b>block buffered</b></i>. In this mode the +currently buffered data is flushed when the buffer is full. This causes +most interactive programs to deadlock. Block buffering is more +efficient when writing to disks and pipes. Take the situation where a +program prints a message "Enter your user name:\n" and then waits for +you type type something. In block buffered mode, the stdio library will +not put the message into the pipe even though a linefeed is printed. +The result is that you never receive the message, yet the child +application will sit and wait for you to type a response. Don't confuse +the stdio lib's buffer with the pipe's buffer. The pipe buffer is +another area that can cause problems. You could flush the input side of +a pipe, whereas you have no control over the stdio library buffer. </p> +<p>More information: the Standard IO library has three states for a +FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered; +and _IONBF for unbuffered. The STDIO lib will use block buffering when +talking to a block file descriptor such as a pipe. This is usually not +helpful for interactive programs. Short of recompiling your program to +include fflush() everywhere or recompiling a custom stdio library there +is not much a controlling application can do about this if talking over +a pipe.</p> +<p> The program may have put data in its output that remains unflushed +because the output buffer is not full; then the program will go and +deadlock while waiting for input -- because you never send it any +because you are still waiting for its output (still stuck in the +STDIO's output buffer).</p> +<p>The answer is to use a pseudo-tty. A TTY device will force <i><b>line</b></i> +buffering (as opposed to block buffering). Line buffering means that +you will get each line when the child program sends a line feed. This +corresponds to the way most interactive programs operate -- send a line +of output then wait for a line of input.</p> +<p>I put "answer" in quotes because it's ugly solution and because +there is no POSIX standard for pseudo-TTY devices (even though they +have a TTY standard...). What would make more sense to me would be to +have some way to set a mode on a file descriptor so that it will tell +the STDIO to be line-buffered. I have investigated, and I don't think +there is a way to set the buffered state of a child process. The STDIO +Library does not maintain any external state in the kernel or whatnot, +so I don't think there is any way for you to alter it. I'm not quite +sure how this line-buffered/block-buffered state change happens +internally in the STDIO library. I think the STDIO lib looks at the +file descriptor and decides to change behavior based on whether it's a +TTY or a block file (see isatty()).</p> +<p>I hope that this qualifies as helpful.</p> + +<h1>Don't use a pipe to control another application...</h1> +<p>Pexpect may seem similar to <span class="code">os.popen()</span> or +<span class="code">commands</span> module. The main difference is that +Pexpect (like Expect) uses a pseudo-TTY to talk to the child +application. Most applications do no work well through the system() +call or through pipes. And probably all applications that ask a user to +type in a password will fail. These applications bypass the stdin and +read directly from the TTY device. Many applications do not explicitly +flush their output buffers. This causes deadlocks if you try to control +an interactive application using a pipe. What happens is that most UNIX +applications use the stdio (#include <stdio.h>) for input and +output. The stdio library behaves differently depending on where the +output is going. There is no way to control this behavior from the +client end.<br> +</p> + +<p><b>Q: Can I do screen scraping with this thing?</b></p> +<p>A: That depends. If your application just does line-oriented output +then this is easy. If it does screen-oriented output then it may work, +but it could be hard. For example, trying to scrape data from the 'top' +command would be hard. The top command repaints the text window. </p> +<p>I am working on an ANSI / VT100 terminal emulator that will have +methods to get characters from an arbitrary X,Y coordinate of the +virtual screen. It works and you can play with it, but I have no +working examples at this time.</p> +<hr noshade="noshade" size="1"> +<h1><a name="bugs"></a>Bugs</h1> +<h2>Threads</h2> +<p>On Linux (RH 8) you cannot spawn a child from a different thread and +pass the handle back to a worker thread. The child is successfully +spawned but you can't interact with it. The only way to make it work is +to spawn and interact with the child all in the same thread. [Adam +Kerrison] </p> +<h2><a name="echo_bug"></a>Timing issue with send() and sendline()</h2> +<p>This problem has been addressed and should not effect most users.</p> +<p>It is sometimes possible to read an echo of the string sent with <span + class="code">send()</span> and <span class="code">sendline()</span>. +If you call <span class="code">sendline()</span> and then immediately +call <span class="code">readline()</span> you may get part of your +output echoed back. You may read back what you just wrote even if the +child application does not explicitly echo it. Timing is critical. This +could be a security issue when talking to an application that asks for +a password; otherwise, this does not seem like a big deal. <i>But why +do TTYs do this</i>?</p> +<p>People usually report this when they are trying to control SSH or +some other login. For example, if your code looks something like this: </p> +<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre> +<p><br> +<blockquote> +1. SSH prints "password:" prompt to the user.<br> +2. SSH turns off echo on the TTY device.<br> +3. SSH waits for user to enter a password.<br> +</blockquote> +When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt +before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between +steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug. +</p> +<p> +Pexpect now automatically adds a short delay before sending data to a child process. +This more closely mimics what happens in the usual human-to-app interaction. +The delay can be tuned with the 'delaybeforesend' attribute of the spawn class. +In general, this fixes the problem for everyone and so this should not be an issue +for most users. For some applications you might with to turn it off. + child = pexpect.spawn ("ssh user@example.com") + child.delaybeforesend = 0 +</p> +<p><br> +</p> +<p>Try changing it to look like the following. I know that this fix +does not look correct, but it works. I have not figured out exactly +what is happening. You would think that the sleep should be after the +sendline(). The fact that the sleep helps when it's between the +expect() and the sendline() must be a clue.</p> +<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre> +<h2>Timing issue with isalive()</h2> +<p>Reading the state of isalive() immediately after a child exits may +sometimes return 1. This is a race condition. The child has closed its +file descriptor, but has not yet fully exited before Pexpect's +isalive() executes. Addings a slight delay before the isalive() will +help. In the following example <span class="code">isalive()</span> +sometimes returns 1:</p> +<blockquote> + <pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>print child.isalive()</pre> +</blockquote> +<p>But if there is any delay before the call to <span class="code">isalive()</span> +then it will always return 0 as expected.</p> +<blockquote> + <pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>time.sleep(0.1)<br>print child.isalive()</pre> +</blockquote> + +<h2>Truncated output just before child exits</h2> +<p><i>So far I have seen this only on older versions of <b>Apple's MacOS X</b>.</i> +If the child application quits it may not flush its output buffer. This +means that your Pexpect application will receive an EOF even though it +should have received a little more data before the child died. This is +not generally a problem when talking to interactive child applications. +One example where it is a problem is when trying to read output from a +program like '<span class="code">ls</span>'. You may receive most of +the directory listing, but the last few lines will get lost before you +receive an EOF. The reason for this is that '<span class="code">ls</span>' +runs; completes its task; and then exits. The buffer is not flushed +before exit so the last few lines are lost. The following example +demonstrates the problem:</p> +<p> </p> +<blockquote> + <pre class="code">child = pexpect.spawn ('ls -l')<br>child.expect (pexpect.EOF)<br>print child.before <br> </pre> +</blockquote> +<p></p> + +<h2>Controlling SSH on Solaris</h2> +<p>Pexpect does not yet work perfectly on Solaris. +One common problem is that SSH sometimes will not allow TTY password +authentication. For example, you may expect SSH to ask you for a +password using code like this: +</p> +<pre class="code">child = pexpect.spawn ('ssh user@example.com')<br>child.expect ('assword')<br>child.sendline ('mypassword')<br></pre> +You may see the following error come back from a spawned +child SSH: +<p></p> +<blockquote>Permission denied (publickey,keyboard-interactive). </blockquote> +<p> +This means that SSH thinks it can't access the TTY to ask you for your +password. +The only solution I have found is to use public key authentication with +SSH. +This bypasses the need for a password. I'm not happy with this +solution. +The problem is due to poor support for Solaris Pseudo TTYs in the +Python +Standard Library. </p> +<hr noshade="noshade" size="1"> +<h1><a name="changes"></a>CHANGES</h1> +<h2>Current Release</h2> +<p>Fixed OSError exception when a pexpect object is cleaned up. +Previously you might have seen this exception:</p> +<blockquote> + <pre class="code">Exception exceptions.OSError: (10, 'No child processes') <br>in <bound method spawn.__del__ of<br><pexpect.spawn instance at 0xd248c>> ignored</pre> +</blockquote> +<p>You should not see that anymore. Thanks to Michael Surette.</p> +<p>Added support for buffering reads. This greatly improves speed when +trying to match long output from a child process. When you create an +instance of the spawn object you can then set a buffer size. For now +you MUST do the following to turn on buffering -- it may be on by +default in future version.</p> +<blockquote> + <pre class="code">child = pexpect.spawn ('my_command')<br>child.maxread=1000 # Sets buffer to 1000 characters.</pre> +</blockquote> +<div> +<p>I made a subtle change to the way TIMEOUT and EOF exceptions behave. +Previously you could either expect these states in which case pexpect +will not raise an exception, or you could just let pexpect raise an +exception when these states were encountered. If you expected the +states then the 'before' property was set to everything before the +state was encountered, but if you let pexpect raise the exception then +'before' was not set. Now the 'before' property will get set either way +you choose to handle these states.</p> +<h2><i>Older changes...</i></h2> +<p>The spawn object now provides iterators for a <i>file-like interface</i>. +This makes Pexpect a more complete file-like object. You can now write +code like this:</p> +<blockquote> + <pre class="code">child = pexpect.spawn ('ls -l')<br>for line in child:<br> print line<br></pre> +</blockquote> +<p>I added the attribute <span class="code">exitstatus</span>. This +will give the exit code returned by the child process. This will be set +to <span class="code">None</span> while the child is still alive. When +<span class="code">isalive()</span> returns 0 then <span class="code">exitstatus</span> +will be set.</p> +<p>I made a few more tweaks to <span class="code">isalive()</span> so +that it will operate more consistently on different platforms. Solaris +is the most difficult to support.</p> +<p> </p> +<p>You can now put <span class="code">TIMEOUT</span> in a list of +expected patterns. This is just like putting <span class="code">EOF</span> +in the pattern list. Expecting for a <span class="code">TIMEOUT</span> +may not be used as often as <span class="code">EOF</span>, but this +makes Pexpect more consitent.</p> +<p>Thanks to a suggestion and sample code from Chad J. Schroeder I +added the ability for Pexpect to operate on a file descriptor that is +already open. This means that Pexpect can be used to control streams +such as those from serial port devices. Now you just pass the integer +file descriptor as the "command" when contsructing a spawn open. For +example on a Linux box with a modem on ttyS1:</p> +<blockquote> + <pre class="code">fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)<br>m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.<br>m.send("+++") # Escape sequence<br>m.send("ATZ0\r") # Reset modem to profile 0<br>rval = m.expect(["OK", "ERROR"])</pre> +</blockquote> +<h3>Pexpect now tests itself on Compile Farm!</h3> +<p>I wrote a nice script that uses ssh to connect to each machine on +Source Forge's Compile Farm and then run the testall.py script for each +platform. The result of the test is then recorded for each platform. +Now it's easy to run regression tests across multiple platforms.</p> +<h3>Pexpect is a file-like object</h3> +<p>The spawn object now provides a <i>file-like interface</i>. It +supports most of the methods and attributes defined for Python File +Objects. </p> +<p>I changed write and writelines() so that they no longer return a +value. Use send() if you need that functionality. I did this to make +the Spawn object more closely match a file-like object.</p> +<p>read() was renamed to read_nonblocking(). I added a new read() +method that matches file-like object interface. In general, you should +not notice the difference except that read() no longer allows you to +directly set the timeout value. I hope this will not effect any +existing code. Switching to read_nonblocking() should fix existing code.</p> +<p>I changed the name of <span class="code">set_echo()</span> to <span + class="code">setecho()</span>.</p> +<p>I changed the name of <span class="code">send_eof()</span> to <span + class="code">sendeof()</span>.</p> +<p>I modified <span class="code">kill()</span> so that it checks to +make sure the pid isalive().</p> +<p>I modified <span class="code">spawn()</span> (really called from <span + class="code">__spawn()</span>)so that it does not raise an expection +if <span class="code">setwinsize()</span> fails. Some platforms such +as Cygwin do not like setwinsize. This was a constant problem and since +it is not a critical feature I decided to just silence the error. +Normally I don't like to do that, but in this case I'm making an +exception.</p> +<p>Added a method <span class="code">close()</span> that does what you +think. It closes the file descriptor of the child application. It makes +no attempt to actually kill the child or wait for its status. </p> +<p>Add variables <span class="code">__version__</span> and <span + class="code">__revision__</span> (from cvs) to the pexpect modules. +This is mainly helpful to me so that I can make sure that I'm testing +with the right version instead of one already installed.</p> +<h3>Logging changes</h3> +<blockquote> + <p><span class="code">log_open()</span> and <span class="code">log_close()</span> +have been removed. Now use <span class="code">setlog()</span>. The <span + class="code">setlog()</span> method takes a file object. This is far +more flexible than the previous log method. Each time data is written +to the file object it will be flushed. To turn logging off simply call <span + class="code">setlog()</span> with None.</p> +</blockquote> +<h2>isalive changes</h2> +<blockquote> + <p>I renamed the <span class="code">isAlive()</span> method to <span + class="code">isalive()</span> to match the more typical naming style +in Python. Also the technique used to detect child process status has +been drastically modified. Previously I did some funky stuff with +signals which caused indigestion in other Python modules on some +platforms. It's was a big headache. It still is, but I think it works +better now.</p> +</blockquote> +<h3>attribute name changes</h3> +<blockquote> + <p>The names of some attributes have been changed. This effects the +names of the attributes that are set after called the <span + class="code">expect()</span> method.</p> + <table class="pymenu" border="0" cellpadding="5"> + <tbody> + <tr> + <th class="pymenu">NEW NAME</th> + <th class="pymenu">OLD NAME</th> + </tr> + <tr> + <td><span class="code">before</span><br> + <i>Everything before the match.</i></td> + <td><span class="code">before</span></td> + </tr> + <tr> + <td><span class="code">after</span><br> + <i>Everything after and including the first character of the +match</i></td> + <td><span class="code">matched</span></td> + </tr> + <tr> + <td><span class="code">match</span><br> + <i>This is the re MatchObject from the match.<br> +You can get groups() from this.<br> +See '<span class="code">uptime.py</span>' in the examples tar ball.</i></td> + <td><i>New -- Did not exist</i></td> + </tr> + </tbody> + </table> +</blockquote> +<h3>EOF changes</h3> +<blockquote> + <p>The <span class="code">expect_eof()</span> method is gone. You +can now simply use the <span class="code">expect()</span> method to +look for EOF.</p> + <p>Was:</p> + <blockquote> + <p><span class="code">p.expect_eof ()</span></p> + </blockquote> + <p>Now:</p> + <blockquote> + <p><span class="code">p.expect (pexpect.EOF)</span></p> + </blockquote> +</blockquote> +<hr noshade="noshade" size="1"> +<h1><a name="testing"></a>TESTING</h1> +<p>The following platforms have been tested:</p> +<!-- +<table class="pymenu" border="0" cellpadding="5"> + <tbody> + <tr> + <th class="pymenu">PLATFORM</th> + <th class="pymenu">RESULTS</th> + </tr> + <tr> + <td>Linux 2.4.9-ac10-rmk2-np1-cerf2<br> +armv4l</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Linux 2.4.18 #2<br> +sparc64</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>MacOS X Darwin Kernel Version 5.5<br> +powerpc</td> + <td> + <p>failed more than one test.</p> + <p>Generally Pexpect works on OS X, but the nature of the quirks +cause a many of the tests to fail. See <a href="#bugs">bugs</a> +(Incomplete Child Output). The problem is more than minor, but Pexpect +is still more than useful for most tasks. The problem is an edge case.</p> + </td> + </tr> + <tr> + <td>Linux 2.2.20<br> +alpha<br> + </td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Linux 2.4.18-5smp<br> +i686</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>OpenBSD 2.9 GENERIC#653<br> +i386</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Solaris</td> + <td> + <p>failed <span class="code">test_destructor</span></p> + <p>Otherwise, this is working pretty well. The destructor problem +is minor. For some reason, the <i>second</i> time a pty file +descriptor is created and deleted it never gets returned for use. It +does not effect the first time or the third time or any time after +that. It's only the second time. This is weird... This could be a file +descriptor leak, or it could be some peculiarity of how Solaris +recycles them. I thought it was a UNIX requirement for the OS to give +you the lowest available filedescriptor number. In any case, this +should not be a problem unless you create hundreds of pexpect +instances... It may also be a pty module bug. </p> + </td> + </tr> + <tr> + <td>Windows XP Cygwin</td> + <td>failed <span class="code">test_destructor</span>. That it +works at all is amazing to me. Cygwin rules!</td> + </tr> + </tbody> +</table> +--> +<h1> </h1> +<h1><a name="todo">TO DO</a></h1> +<p>Add an option to add a delay after each expect() or before each +read()/readline() call to automatically avoid the <a href="#echo_bug">echo +bug</a>.</p> +<p> </p> +</div> +<hr noshade="noshade" size="1"> +<table border="0"> + <tbody> + <tr> + <td> <a href="http://www.noah.org/email/"><img src="email.png" + alt="Click to send email." border="0" height="16" width="100"></a> </td> + </tr> + </tbody> +</table> +</div> +<div id="Menu"><b>INDEX</b><br> +<hr noshade="noshade" size="1"> <a href="#license" + title="Python Software Foundation License">License</a><br> +<a href="#download" title="Download and setup instructions">Download</a><br> +<a href="#doc" title="Documentation and overview">Documentation</a><br> +<a href="#status" title="Project Status">Project Status</a><br> +<a href="#requirements" title="System requirements to use Pexpect">Requirements</a><br> +<a href="#overview" title="Overview of what Pexpect does">Overview</a><br> +<a href="#faq" title="FAQ">FAQ</a><br> +<a href="#bugs" title="Bugs and work-arounds">Known Bugs</a><br> +<a href="#changes" title="What's new with Pexpect">Recent Changes</a><br> +<a href="#testing" title="Test results on various platforms">Testing</a><br> +<a href="#todo" title="What to do next">To do</a><br> +<a href="http://pexpect.svn.sourceforge.net/viewvc/pexpect/trunk/pexpect/" title="browse SVN">Browse SVN</a><br> +<br> +<a href="http://sourceforge.net/projects/pexpect/" + title="The Pexpect project page on SourceForge.net"> <img + src="http://sourceforge.net/sflogo.php?group_id=59762&type=5" + alt="The Pexpect project page on SourceForge.net" border="0" + height="31" width="105"> </a> </div> +</body> +</html> diff --git a/third_party/Python/module/pexpect-2.4/doc/index.template.html b/third_party/Python/module/pexpect-2.4/doc/index.template.html new file mode 100644 index 000000000000..ab63b2dec963 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/doc/index.template.html @@ -0,0 +1,868 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<title>Pexpect - a Pure Python Expect-like module</title> +<link rel="stylesheet" href="clean.css" type="text/css"> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<meta name="Author" content="Noah Spurrier"> +<meta name="Keywords" + content="pexpect, Noah Spurrier, pypect, Python, Libes, TCL, Expect, pipe, popen, pyExpect, expectpy, expect-like, expect-alike, expect like"> +<meta name="Description" + content="Pexpect is a pure Python Expect-like module. Pexpect makes Python a better tool for controlling other applications."> +</head> +<body bgcolor="#ffffff" text="#000000"> +<div id="Header"> +<h1>Pexpect version VERSION<br> +a Pure Python Expect-like module +</h1> +</div> +<div id="Content"> +<p>Pexpect makes Python a better tool for controlling other +applications.</p> +<p>Pexpect is a pure Python module for spawning child applications; +controlling them; and responding to expected patterns in their output. +Pexpect works like Don Libes' Expect. Pexpect allows your script to +spawn a child application and control it as if a human were typing +commands.</p> +<p>Pexpect can be used for automating interactive applications such as +ssh, ftp, passwd, telnet, etc. It can be used to a automate setup +scripts for duplicating software package installations on different +servers. It can be used for automated software testing. Pexpect is in +the spirit of Don Libes' Expect, but Pexpect is pure Python. Unlike +other Expect-like modules for Python, Pexpect does not require TCL or +Expect nor does it require C extensions to be compiled. It should work +on any platform that supports the standard Python pty module. The +Pexpect interface was designed to be easy to use.</p> +<table border="0"> + <tbody> + <tr> + <td align="right" valign="top">Send questions to:</td> + <td align="left"><a href="http://www.noah.org/email/"><img + src="email.png" alt="Click to send email." border="0" height="16" + width="100"></a></td> + </tr> + </tbody> +</table> +<hr noshade="noshade" size="1"> +<h1><a name="license"></a>License: MIT style</h1> +<p> +Free, open source, and all that good stuff.<br> +<br> +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions:<br> +<br> +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software.<br> +<br> +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE.<br> +<br> +Pexpect Copyright (c) 2008 Noah Spurrier<br> +http://pexpect.sourceforge.net/ +</p> + +<hr noshade="noshade" size="1"> +<h1><a name="download"></a><a + href="http://sourceforge.net/project/showfiles.php?group_id=59762">Download</a></h1> +<p>Download the <a + href="http://sourceforge.net/project/showfiles.php?group_id=59762"> +current version here</a> from the SourceForge site. Grab the current Pexpect tarball. +</p> +<h2>Installing Pexpect</h2> +<p>The Pexpect tarball is a standard Python Distutil distribution.</p> +<ol> + <li>download <span class="code">pexpect-VERSION.tar.gz</span></li> + <li><span class="code">tar zxf pexpect-VERSION.tar.gz</span></li> + <li><span class="code">cd pexpect-VERSION</span></li> + <li><span class="code">python setup.py install</span> <i>do this as root</i></li> +</ol> +<h2>Examples</h2> +<p> +Under the <span class="code">pexpect-VERSION</span> directory you should find +the <span class="code">examples</span> directory. +This is the best way to learn to use Pexpect. +See the descriptions of <a href="examples.html">Pexpect Examples</a>. +</p> +<h2><a name="doc"></a>API Documentation</h2> +<p> +<blockquote> +<a href="pexpect.html">pexpect</a> This is the main module that you want.<br> +<a href="pxssh.html">pxssh</a> Pexpect SSH is an extension of 'pexpect.spawn' that specializes in SSH.<br> +</blockquote> +the following are experimental extensions to Pexpect<br> +<blockquote> +<a href="fdpexpect.html">fdpexpect</a> fdpexpect extension of 'pexpect.spawn' that uses an open file descriptor.<br> +<a href="screen.html">SCREEN</a> This represents a virtual 'screen'.<br> +<a href="ANSI.html">ANSI</a> This parses ANSI/VT-100 terminal escape codes.<br> +<a href="FSM.html">FSM</a> This is a finite state machine used by ANSI.<br> +</blockquote> +</p> +<hr noshade="noshade" size="1"> +<h1><a name="status"></a>Project Status</h1> +<p>Automated pyunit tests reach over 80% +code coverage on pexpect.py. I regularly test on Linux and BSD +platforms. I try to test on Solaris and Irix. +</p> +<hr noshade="noshade" size="1"> +<h1><a name="requirements"></a>Requirements for use of Pexpect</h1> +<h2>Python</h2> +<blockquote> + <p>Pexpect was written and tested with Python 2.4. It should work on +earlier versions that have the <span class="code">pty</span> module. I +sometimes even manually test it with Python 1.5.2, but I can't easily +run the PyUnit test framework against Python 1.5.2, so I have less +confidence in Pexpect on Python 1.5.2.</p> +</blockquote> +<h2>pty module</h2> +<blockquote> + <p>Any POSIX system (UNIX) with a working <span class="code">pty</span> +module should be able to run Pexpect. The <span class="code">pty</span> +module is part of the Standard Python Library, so if you are running on +a POSIX system you should have it. The <span class="code">pty</span> +module does not run the same on all platforms. It should be solid on Linux +and BSD systems. I have taken effort to try to smooth the wrinkles out of the different platforms. To learn more +about the wrinkles see <a href="#bugs">Bugs</a> and <a href="#testing">Testing</a>.</p> +</blockquote> +<p>Pexpect does not currently work on the standard Windows Python (see +the pty requirement); however, it seems to work fine using <a + href="http://www.cygwin.com/">Cygwin</a>. It is possible to build +something like a pty for Windows, but it would have to use a different +technique that I am still investigating. I know it's possible because +Libes' Expect was ported to Windows. <i>If you have any ideas or +skills to contribute in this area then I would really appreciate some +tips on how to approach this problem.</i> </p> +<hr noshade="noshade" size="1"> +<h1><a name="overview"></a>Overview</h1> +<p>Pexpect can be used for automating interactive applications such as +ssh, ftp, mencoder, passwd, etc. The Pexpect interface was designed to be +easy to use. Here is an example of Pexpect in action:</p> +<blockquote> + <pre class="code"># This connects to the openbsd ftp site and<br># downloads the recursive directory listing.<br>import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('cd pub')<br>child.expect('ftp> ')<br>child.sendline ('get ls-lR.gz')<br>child.expect('ftp> ')<br>child.sendline ('bye')<br></pre> +</blockquote> +<p> Obviously you could write an ftp client using Python's own <span + class="code">ftplib</span> module, but this is just a demonstration. +You can use this technique with any application. This is especially +handy if you are writing automated test tools.</p> + +<p>There are two important methods in Pexpect -- <span class="code"><b>expect()</b></span> +and <span class="code"><b>send()</b></span> (or <span class="code">sendline()</span> +which is like <span class="code">send()</span> with a linefeed). +The <span class="code">expect()</span> method waits for the child application +to return a given string. The string you specify is a regular expression, so +you can match complicated patterns. The <span class="code"><b>send()</b></span> method +writes a string to the child application. From the child's point of +view it looks just like someone typed the text from a terminal. After +each call to <span class="code"><b>expect()</b></span> the <span + class="code"><b>before</b></span> and <span class="code"><b>after</b></span> +properties will be set to the text printed by child application. The <span + class="code"><b>before</b></span> property will contain all text up to +the expected string pattern. The <span class="code"><b>after</b></span> string +will contain the text that was matched by the expected pattern. +The <span class="code">match</span> property is set to the <span class="code">re MatchObject</span>. +</p> + +<p>An example of Pexpect in action may make things more clear. This example uses +<span class="code">ftp</span> to login to the OpenBSD site; list files +in a directory; and then pass interactive control of the ftp session to +the human user.</p> +<blockquote> + <pre class="code">import pexpect<br>child = pexpect.spawn ('ftp ftp.openbsd.org')<br>child.expect ('Name .*: ')<br>child.sendline ('anonymous')<br>child.expect ('Password:')<br>child.sendline ('noah@example.com')<br>child.expect ('ftp> ')<br>child.sendline ('ls /pub/OpenBSD/')<br>child.expect ('ftp> ')<br>print child.before # Print the result of the ls command.<br>child.interact() # Give control of the child to the user.<br></pre> +</blockquote> +<h2>Special EOF and TIMEOUT patterns</h2> +<p> +There are two special patterns to match the End Of File or a Timeout condition. +You you can pass these patterns to <span class="code">expect()</span>. +These patterns are not regular expressions. Use them like predefined constants. +</p> +<p>If the child has died and you have read all the child's output then ordinarily +<span class="code">expect()</span> will raise an <span class="code">EOF</span> +exception. You can read everything up to the EOF without generating an +exception by using the EOF pattern <span class="code">expect(pexpect.EOF)</span>. +In this case everything the child has output will be available in the <span + class="code">before</span> property.</p> +<p>The pattern given to <span class="code">expect()</span> may be a +regular expression or it may also be a <b>list</b> of regular expressions. +This allows you to match multiple optional responses. The <span class="code">expect()</span> +method returns the index of the pattern that was matched. For example, +say you wanted to login to a server. After entering a password you +could get various responses from the server -- your password could be +rejected; or you could be allowed in and asked for your terminal type; +or you could be let right in and given a command prompt. The following +code fragment gives an example of this:</p> +<blockquote> + <pre class="code">child.expect('password:')<br>child.sendline (my_secret_password)<br># We expect any of these three patterns...<br>i = child.expect (['Permission denied', 'Terminal type', '[#\$] '])<br>if i==0:<br> print 'Permission denied on host. Can't login'<br> child.kill(0)<br>elif i==2:<br> print 'Login OK... need to send terminal type.'<br> child.sendline('vt100')<br> child.expect ('[#\$] ')<br>elif i==3:<br> print 'Login OK.'<br> print 'Shell command prompt', child.after</pre> +</blockquote> +<p>If nothing matches an expected pattern then expect will eventually +raise a TIMEOUT exception. The default time is 30 seconds, but you can +change this by passing a timeout argument to expect():</p> +<blockquote> + <pre class="code"># Wait no more than 2 minutes (120 seconds) for password prompt.<br>child.expect('password:', timeout=120)</pre> +</blockquote> +<h2>Find the end of line -- CR/LF conventions<br> +Matching at the end of a line can be tricky<br> +$ regex pattern is useless.<br> +</h2> +<p>Pexpect matches regular expressions a little differently than what +you might be used to. +</p> +<p><i><b>The $ pattern for end of line match is useless</b></i>. +The $ matches the end of string, but Pexpect reads from the child +one character at a time, so each character looks like the end of a line. +Pexpect can't do a look-ahead into the child's output stream. +In general you would have this situation when using regular expressions +with any stream.<br> +<i>Note, pexpect does have an internal buffer, so reads are faster +than one character at a time, but from the user's perspective the regex +patterns test happens one character at a time.</i></p> +<p>The best way to match the end of a line is to look for the +newline: "\r\n" (CR/LF). Yes, that does appear to be DOS-style. +It may surprise some UNIX people to learn that terminal TTY device drivers +(dumb, vt100, ANSI, xterm, etc.) all use the CR/LF combination to signify +the end of line. Pexpect uses a Pseudo-TTY device to talk to the child application, so +when the child app prints "\n" you actually see "\r\n". +</p> +<p><b>UNIX uses just linefeeds to end lines of text, but not when it +comes to TTY devices!</b> TTY devices are more like the Windows world. +Each line of text end with a CR/LF combination. When you intercept data +from a UNIX command from a TTY device you will find that the TTY device +outputs a CR/LF combination. A UNIX command may only write a linefeed +(\n), but the TTY device driver converts it to CR/LF. This means that +your terminal will see lines end with CR/LF (hex <span class="code">0D 0A</span>). +Since Pexpect emulates a terminal, to match ends of lines you have to +expect the CR/LF combination.</p> +<blockquote> + <p class="code">child.expect ('\r\n')</p> +</blockquote> +<p>If you just need to skip past a new line then <span class="code">expect +('\n')</span> by itself will work, but if you are expecting a specific +pattern before the end of line then you need to explicitly look for the +\r. For example the following expects a word at the end of a line:</p> +<blockquote> + <p class="code">child.expect ('\w+\r\n')</p> +</blockquote> +<p>But the following would both fail:</p> +<blockquote> + <p class="code">child.expect ('\w+\n')</p> +</blockquote> +<p>And as explained before, trying to use '$' to match the end of line +would not work either:</p> +<blockquote> + <p class="code">child.expect ('\w+$')</p> +</blockquote> +<p>So if you need to explicitly look for the END OF LINE, you want to +look for the CR/LF combination -- not just the LF and not the $ pattern.</p> +<p>This problem is not limited to Pexpect. This problem happens any +time you try to perform a regular expression match on a stream. Regular +expressions need to look ahead. With a stream it is hard to look ahead +because the process generating the stream may not be finished. There is no +way to know if the process has paused momentarily or is finished and +waiting for you. <font color="#cc0000">Pexpect must implicitly always +do a NON greedy match (minimal) at the end of a input {### already said +this}.</font> </p> +<p>Pexpect compiles all regular expressions with the DOTALL flag. With +the DOTALL flag a "." will match a newline. See the Python <a + href="http://www.python.org/doc/current/lib/node115.html#l2h-733">documentation</a></p> +<h2>Beware of + and * at the end of input.</h2> +<p>Remember that any time you try to match a pattern that needs +look-ahead that you will always get a minimal match (non greedy). For +example, the following will always return just one character:</p> +<blockquote> + <p class="code">child.expect ('.+')</p> +</blockquote> +<p>This example will match successfully, but will always return no +characters:</p> +<blockquote> + <p class="code">child.expect ('.*')</p> +</blockquote> +<p>Generally any star * expression will match as little as possible</p> +<p>One thing you can do is to try to force a non-ambiguous character at +the end of your <span class="code">\d+</span> pattern. Expect that +character to delimit the string. For example, you might try making the +end of your pattrn be <span class="code">\D+</span> instead of <span + class="code">\D*</span>. That means number digits alone would not +satisfy the (<span class="code">\d+</span>) pattern. You would need +some number(s) and at least one <span class="code">\D</span> at the +end. </p> +<h2>Matching groups</h2> +<p>You can group regular expression using parenthesis. After a match, +the <span class="code">match</span> parameter of the spawn object will +contain the Python Match object. </p> +<h2>Examples</h2> +<p>Using "match" and groups...</p> +<h2>Debugging</h2> +<p>If you get the string value of a pexpect.spawn object you will get +lots of useful debugging information. For debugging it's very useful to +use the following pattern:</p> +<p>try:<br> + i = child.expect ([pattern1, pattern2, pattern3, +etc])<br> +except:<br> + print "Exception was thrown"<br> + print "debug information:"<br> + print str(child)<br> +</p> +<p>It is also useful to log the child's input and out to a file or the +screen. The following will turn on logging and send output to stdout +(the screen).<br> +</p> +<p> child = pexpect.spawn (foo)<br> + child.logfile = sys.stdout<br> +<br> +</p> +<hr noshade="noshade" size="1"> +<h1>Exceptions</h1> +<p><b>EOF</b></p> +<p>Note that two flavors of EOF Exception may be thrown. They are +virtually identical except for the message string. For practical +purposes you should have no need to distinguish between them, but they +do give a little extra information about what type of platform you are +running. The two messages are:</p> +<blockquote> + <p class="code">End Of File (EOF) in read(). Exception style platform.</p> + <p class="code">End Of File (EOF) in read(). Empty string style +platform.</p> +</blockquote> +<p>Some UNIX platforms will throw an exception when you try to read +from a file descriptor in the EOF state. Other UNIX platforms instead +quietly return an empty string to indicate that the EOF state has been +reached.</p> +<p><b>Expecting EOF</b></p> +<p>If you wish to read up to the end of the child's output without +generating an <span class="code">EOF</span> exception then use the <span + class="code">expect(pexpect.EOF)</span> method.</p> +<p><b>TIMEOUT</b></p> +<p>The <span class="code">expect()</span> and <span class="code">read()</span> +methods will also timeout if the child does not generate any output for +a given amount of time. If this happens they will raise a <span + class="code">TIMEOUT</span> exception. You can have these method +ignore a timeout and block indefinitely by passing None for the timeout +parameter.</p> +<blockquote> + <p class="code">child.expect(pexpect.EOF, timeout=None)</p> +</blockquote> +<hr noshade="noshade" size="1"> +<h1><a name="faq"></a>FAQ</h1> +<p><b>Q: Why don't shell pipe and redirect (| and >) work when I +spawn a command?</b></p> +<p> + +A: Remember that Pexpect does NOT interpret shell meta characters such as +redirect, pipe, or wild cards (>, |, or *). That's done by a shell not the +command you are spawning. This is a common mistake. If you want to run a +command and pipe it through another command then you must also start a shell. +For example: + +<pre> + child = pexpect.spawn('/bin/sh -c "ls -l | grep LOG > log_list.txt"') + child.expect(pexpect.EOF) +</pre> + +The second form of spawn (where you pass a list of arguments) is useful in +situations where you wish to spawn a command and pass it its own argument list. +This can make syntax more clear. For example, the following is equivalent to +the previous example: + +<pre> + shell_cmd = 'ls -l | grep LOG > log_list.txt' + child = pexpect.spawn ('/bin/sh', ['-c', shell_cmd]) + child.expect (pexpect.EOF) +</pre> + +</p> +<p><b>Q: Isn't there already a Python Expect?</b></p> +<p>A: Yes, there are several of them. They usually require you to +compile C. I wanted something that was pure Python and preferably a +single module that was simple to install. I also wanted something that +was easy to use. This pure Python expect only recently became possible +with the introduction of the pty module in the standard Python library. +Previously C extensions were required.</p> + +<p><strong>Q: The before and after properties sound weird.</strong></p> +<p>Originally I was going to model Pexpect more after Expect, but then +I found that I could never remember how to get the context of the stuff +I was trying to parse. I hate having to read my own documentation. I +decided that it was easier for me to remember what before and after +was. It just so happens that this is how the -B and -A options in grep +works, so that made it even easier for me to remember. Whatever makes +my life easier is what's best.</p> + +<p><b>Q: Why not just use Expect?</b></p> +<p>A: I love it. It's great. I has bailed me out of some real jams, but +I wanted something that would do 90% of what I need from Expect; be 10% +of the size; and allow me to write my code in Python instead of TCL. +Pexpect is not nearly as big as Expect, but Pexpect does everything I +have ever used Expect for. +<!-- :-P If I liked TCL then you wouldn't be reading this. My appologies to Don Libes -- Expect is cool, TK is cool, but TCL is only slightly better than Perl in my book. Hopefully after Expyct is done I will not need to use Expect anymore -- except for that lovely autoexpect tool. Damn, I wish I had that! --> </p> + +<p><b>Q: Why not just use a pipe (popen())?</b></p> +<p>A: A pipe works fine for getting the output to non-interactive +programs. If you just want to get the output from <span class="code">ls</span>, +<span class="code">uname</span>, or <span class="code">ping</span> +then this works. Pipes do not work very well for interactive programs +and pipes will almost certainly fail for most applications that ask for +passwords such as telnet, ftp, or ssh.</p> +<p>There are two reasons for this. </p> +<p>First an application may bypass stdout and print directly to its +controlling TTY. Something like SSH will do this when it asks you for a +password. This is why you cannot redirect the password prompt because +it does not go through stdout or stderr.</p> +<p>The second reason is because most applications are built using the C +Standard IO Library (anything that uses <span class="code">#include +<stdio.h></span>). One of the features of the stdio library is +that it buffers all input and output. Normally output is <b><i>line +buffered</i></b> when a program is printing to a TTY (your terminal +screen). Every time the program prints a line-feed the currently +buffered data will get printed to your screen. The problem comes when +you connect a pipe. The stdio library is smart and can tell that it is +printing to a pipe instead of a TTY. In that case it switches from line +buffer mode to <i><b>block buffered</b></i>. In this mode the +currently buffered data is flushed when the buffer is full. This causes +most interactive programs to deadlock. Block buffering is more +efficient when writing to disks and pipes. Take the situation where a +program prints a message "Enter your user name:\n" and then waits for +you type type something. In block buffered mode, the stdio library will +not put the message into the pipe even though a linefeed is printed. +The result is that you never receive the message, yet the child +application will sit and wait for you to type a response. Don't confuse +the stdio lib's buffer with the pipe's buffer. The pipe buffer is +another area that can cause problems. You could flush the input side of +a pipe, whereas you have no control over the stdio library buffer. </p> +<p>More information: the Standard IO library has three states for a +FILE *. These are: _IOFBF for block buffered; _IOLBF for line buffered; +and _IONBF for unbuffered. The STDIO lib will use block buffering when +talking to a block file descriptor such as a pipe. This is usually not +helpful for interactive programs. Short of recompiling your program to +include fflush() everywhere or recompiling a custom stdio library there +is not much a controlling application can do about this if talking over +a pipe.</p> +<p> The program may have put data in its output that remains unflushed +because the output buffer is not full; then the program will go and +deadlock while waiting for input -- because you never send it any +because you are still waiting for its output (still stuck in the +STDIO's output buffer).</p> +<p>The answer is to use a pseudo-tty. A TTY device will force <i><b>line</b></i> +buffering (as opposed to block buffering). Line buffering means that +you will get each line when the child program sends a line feed. This +corresponds to the way most interactive programs operate -- send a line +of output then wait for a line of input.</p> +<p>I put "answer" in quotes because it's ugly solution and because +there is no POSIX standard for pseudo-TTY devices (even though they +have a TTY standard...). What would make more sense to me would be to +have some way to set a mode on a file descriptor so that it will tell +the STDIO to be line-buffered. I have investigated, and I don't think +there is a way to set the buffered state of a child process. The STDIO +Library does not maintain any external state in the kernel or whatnot, +so I don't think there is any way for you to alter it. I'm not quite +sure how this line-buffered/block-buffered state change happens +internally in the STDIO library. I think the STDIO lib looks at the +file descriptor and decides to change behavior based on whether it's a +TTY or a block file (see isatty()).</p> +<p>I hope that this qualifies as helpful.</p> + +<h1>Don't use a pipe to control another application...</h1> +<p>Pexpect may seem similar to <span class="code">os.popen()</span> or +<span class="code">commands</span> module. The main difference is that +Pexpect (like Expect) uses a pseudo-TTY to talk to the child +application. Most applications do no work well through the system() +call or through pipes. And probably all applications that ask a user to +type in a password will fail. These applications bypass the stdin and +read directly from the TTY device. Many applications do not explicitly +flush their output buffers. This causes deadlocks if you try to control +an interactive application using a pipe. What happens is that most UNIX +applications use the stdio (#include <stdio.h>) for input and +output. The stdio library behaves differently depending on where the +output is going. There is no way to control this behavior from the +client end.<br> +</p> + +<p><b>Q: Can I do screen scraping with this thing?</b></p> +<p>A: That depends. If your application just does line-oriented output +then this is easy. If it does screen-oriented output then it may work, +but it could be hard. For example, trying to scrape data from the 'top' +command would be hard. The top command repaints the text window. </p> +<p>I am working on an ANSI / VT100 terminal emulator that will have +methods to get characters from an arbitrary X,Y coordinate of the +virtual screen. It works and you can play with it, but I have no +working examples at this time.</p> +<hr noshade="noshade" size="1"> +<h1><a name="bugs"></a>Bugs</h1> +<h2>Threads</h2> +<p>On Linux (RH 8) you cannot spawn a child from a different thread and +pass the handle back to a worker thread. The child is successfully +spawned but you can't interact with it. The only way to make it work is +to spawn and interact with the child all in the same thread. [Adam +Kerrison] </p> +<h2><a name="echo_bug"></a>Timing issue with send() and sendline()</h2> +<p>This problem has been addressed and should not effect most users.</p> +<p>It is sometimes possible to read an echo of the string sent with <span + class="code">send()</span> and <span class="code">sendline()</span>. +If you call <span class="code">sendline()</span> and then immediately +call <span class="code">readline()</span> you may get part of your +output echoed back. You may read back what you just wrote even if the +child application does not explicitly echo it. Timing is critical. This +could be a security issue when talking to an application that asks for +a password; otherwise, this does not seem like a big deal. <i>But why +do TTYs do this</i>?</p> +<p>People usually report this when they are trying to control SSH or +some other login. For example, if your code looks something like this: </p> +<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre> +<p><br> +<blockquote> +1. SSH prints "password:" prompt to the user.<br> +2. SSH turns off echo on the TTY device.<br> +3. SSH waits for user to enter a password.<br> +</blockquote> +When scripting with Pexpect what can happen is that Pexpect will response to the "password:" prompt +before SSH has had time to turn off TTY echo. In other words, Pexpect sends the password between +steps 1. and 2., so the password gets echoed back to the TTY. I would call this an SSH bug. +</p> +<p> +Pexpect now automatically adds a short delay before sending data to a child process. +This more closely mimics what happens in the usual human-to-app interaction. +The delay can be tuned with the 'delaybeforesend' attribute of the spawn class. +In general, this fixes the problem for everyone and so this should not be an issue +for most users. For some applications you might with to turn it off. + child = pexpect.spawn ("ssh user@example.com") + child.delaybeforesend = 0 +</p> +<p><br> +</p> +<p>Try changing it to look like the following. I know that this fix +does not look correct, but it works. I have not figured out exactly +what is happening. You would think that the sleep should be after the +sendline(). The fact that the sleep helps when it's between the +expect() and the sendline() must be a clue.</p> +<pre class="code">child.expect ('[pP]assword:')<br>child.sendline (my_password)</pre> +<h2>Timing issue with isalive()</h2> +<p>Reading the state of isalive() immediately after a child exits may +sometimes return 1. This is a race condition. The child has closed its +file descriptor, but has not yet fully exited before Pexpect's +isalive() executes. Addings a slight delay before the isalive() will +help. In the following example <span class="code">isalive()</span> +sometimes returns 1:</p> +<blockquote> + <pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>print child.isalive()</pre> +</blockquote> +<p>But if there is any delay before the call to <span class="code">isalive()</span> +then it will always return 0 as expected.</p> +<blockquote> + <pre class="code">child = pexpect.spawn('ls')<br>child.expect(pexpect.EOF)<br>time.sleep(0.1)<br>print child.isalive()</pre> +</blockquote> + +<h2>Truncated output just before child exits</h2> +<p><i>So far I have seen this only on older versions of <b>Apple's MacOS X</b>.</i> +If the child application quits it may not flush its output buffer. This +means that your Pexpect application will receive an EOF even though it +should have received a little more data before the child died. This is +not generally a problem when talking to interactive child applications. +One example where it is a problem is when trying to read output from a +program like '<span class="code">ls</span>'. You may receive most of +the directory listing, but the last few lines will get lost before you +receive an EOF. The reason for this is that '<span class="code">ls</span>' +runs; completes its task; and then exits. The buffer is not flushed +before exit so the last few lines are lost. The following example +demonstrates the problem:</p> +<p> </p> +<blockquote> + <pre class="code">child = pexpect.spawn ('ls -l')<br>child.expect (pexpect.EOF)<br>print child.before <br> </pre> +</blockquote> +<p></p> + +<h2>Controlling SSH on Solaris</h2> +<p>Pexpect does not yet work perfectly on Solaris. +One common problem is that SSH sometimes will not allow TTY password +authentication. For example, you may expect SSH to ask you for a +password using code like this: +</p> +<pre class="code">child = pexpect.spawn ('ssh user@example.com')<br>child.expect ('assword')<br>child.sendline ('mypassword')<br></pre> +You may see the following error come back from a spawned +child SSH: +<p></p> +<blockquote>Permission denied (publickey,keyboard-interactive). </blockquote> +<p> +This means that SSH thinks it can't access the TTY to ask you for your +password. +The only solution I have found is to use public key authentication with +SSH. +This bypasses the need for a password. I'm not happy with this +solution. +The problem is due to poor support for Solaris Pseudo TTYs in the +Python +Standard Library. </p> +<hr noshade="noshade" size="1"> +<h1><a name="changes"></a>CHANGES</h1> +<h2>Current Release</h2> +<p>Fixed OSError exception when a pexpect object is cleaned up. +Previously you might have seen this exception:</p> +<blockquote> + <pre class="code">Exception exceptions.OSError: (10, 'No child processes') <br>in <bound method spawn.__del__ of<br><pexpect.spawn instance at 0xd248c>> ignored</pre> +</blockquote> +<p>You should not see that anymore. Thanks to Michael Surette.</p> +<p>Added support for buffering reads. This greatly improves speed when +trying to match long output from a child process. When you create an +instance of the spawn object you can then set a buffer size. For now +you MUST do the following to turn on buffering -- it may be on by +default in future version.</p> +<blockquote> + <pre class="code">child = pexpect.spawn ('my_command')<br>child.maxread=1000 # Sets buffer to 1000 characters.</pre> +</blockquote> +<div> +<p>I made a subtle change to the way TIMEOUT and EOF exceptions behave. +Previously you could either expect these states in which case pexpect +will not raise an exception, or you could just let pexpect raise an +exception when these states were encountered. If you expected the +states then the 'before' property was set to everything before the +state was encountered, but if you let pexpect raise the exception then +'before' was not set. Now the 'before' property will get set either way +you choose to handle these states.</p> +<h2><i>Older changes...</i></h2> +<p>The spawn object now provides iterators for a <i>file-like interface</i>. +This makes Pexpect a more complete file-like object. You can now write +code like this:</p> +<blockquote> + <pre class="code">child = pexpect.spawn ('ls -l')<br>for line in child:<br> print line<br></pre> +</blockquote> +<p>I added the attribute <span class="code">exitstatus</span>. This +will give the exit code returned by the child process. This will be set +to <span class="code">None</span> while the child is still alive. When +<span class="code">isalive()</span> returns 0 then <span class="code">exitstatus</span> +will be set.</p> +<p>I made a few more tweaks to <span class="code">isalive()</span> so +that it will operate more consistently on different platforms. Solaris +is the most difficult to support.</p> +<p> </p> +<p>You can now put <span class="code">TIMEOUT</span> in a list of +expected patterns. This is just like putting <span class="code">EOF</span> +in the pattern list. Expecting for a <span class="code">TIMEOUT</span> +may not be used as often as <span class="code">EOF</span>, but this +makes Pexpect more consitent.</p> +<p>Thanks to a suggestion and sample code from Chad J. Schroeder I +added the ability for Pexpect to operate on a file descriptor that is +already open. This means that Pexpect can be used to control streams +such as those from serial port devices. Now you just pass the integer +file descriptor as the "command" when contsructing a spawn open. For +example on a Linux box with a modem on ttyS1:</p> +<blockquote> + <pre class="code">fd = os.open("/dev/ttyS1", os.O_RDWR|os.O_NONBLOCK|os.O_NOCTTY)<br>m = pexpect.spawn(fd) # Note integer fd is used instead of usual string.<br>m.send("+++") # Escape sequence<br>m.send("ATZ0\r") # Reset modem to profile 0<br>rval = m.expect(["OK", "ERROR"])</pre> +</blockquote> +<h3>Pexpect now tests itself on Compile Farm!</h3> +<p>I wrote a nice script that uses ssh to connect to each machine on +Source Forge's Compile Farm and then run the testall.py script for each +platform. The result of the test is then recorded for each platform. +Now it's easy to run regression tests across multiple platforms.</p> +<h3>Pexpect is a file-like object</h3> +<p>The spawn object now provides a <i>file-like interface</i>. It +supports most of the methods and attributes defined for Python File +Objects. </p> +<p>I changed write and writelines() so that they no longer return a +value. Use send() if you need that functionality. I did this to make +the Spawn object more closely match a file-like object.</p> +<p>read() was renamed to read_nonblocking(). I added a new read() +method that matches file-like object interface. In general, you should +not notice the difference except that read() no longer allows you to +directly set the timeout value. I hope this will not effect any +existing code. Switching to read_nonblocking() should fix existing code.</p> +<p>I changed the name of <span class="code">set_echo()</span> to <span + class="code">setecho()</span>.</p> +<p>I changed the name of <span class="code">send_eof()</span> to <span + class="code">sendeof()</span>.</p> +<p>I modified <span class="code">kill()</span> so that it checks to +make sure the pid isalive().</p> +<p>I modified <span class="code">spawn()</span> (really called from <span + class="code">__spawn()</span>)so that it does not raise an expection +if <span class="code">setwinsize()</span> fails. Some platforms such +as Cygwin do not like setwinsize. This was a constant problem and since +it is not a critical feature I decided to just silence the error. +Normally I don't like to do that, but in this case I'm making an +exception.</p> +<p>Added a method <span class="code">close()</span> that does what you +think. It closes the file descriptor of the child application. It makes +no attempt to actually kill the child or wait for its status. </p> +<p>Add variables <span class="code">__version__</span> and <span + class="code">__revision__</span> (from cvs) to the pexpect modules. +This is mainly helpful to me so that I can make sure that I'm testing +with the right version instead of one already installed.</p> +<h3>Logging changes</h3> +<blockquote> + <p><span class="code">log_open()</span> and <span class="code">log_close()</span> +have been removed. Now use <span class="code">setlog()</span>. The <span + class="code">setlog()</span> method takes a file object. This is far +more flexible than the previous log method. Each time data is written +to the file object it will be flushed. To turn logging off simply call <span + class="code">setlog()</span> with None.</p> +</blockquote> +<h2>isalive changes</h2> +<blockquote> + <p>I renamed the <span class="code">isAlive()</span> method to <span + class="code">isalive()</span> to match the more typical naming style +in Python. Also the technique used to detect child process status has +been drastically modified. Previously I did some funky stuff with +signals which caused indigestion in other Python modules on some +platforms. It's was a big headache. It still is, but I think it works +better now.</p> +</blockquote> +<h3>attribute name changes</h3> +<blockquote> + <p>The names of some attributes have been changed. This effects the +names of the attributes that are set after called the <span + class="code">expect()</span> method.</p> + <table class="pymenu" border="0" cellpadding="5"> + <tbody> + <tr> + <th class="pymenu">NEW NAME</th> + <th class="pymenu">OLD NAME</th> + </tr> + <tr> + <td><span class="code">before</span><br> + <i>Everything before the match.</i></td> + <td><span class="code">before</span></td> + </tr> + <tr> + <td><span class="code">after</span><br> + <i>Everything after and including the first character of the +match</i></td> + <td><span class="code">matched</span></td> + </tr> + <tr> + <td><span class="code">match</span><br> + <i>This is the re MatchObject from the match.<br> +You can get groups() from this.<br> +See '<span class="code">uptime.py</span>' in the examples tar ball.</i></td> + <td><i>New -- Did not exist</i></td> + </tr> + </tbody> + </table> +</blockquote> +<h3>EOF changes</h3> +<blockquote> + <p>The <span class="code">expect_eof()</span> method is gone. You +can now simply use the <span class="code">expect()</span> method to +look for EOF.</p> + <p>Was:</p> + <blockquote> + <p><span class="code">p.expect_eof ()</span></p> + </blockquote> + <p>Now:</p> + <blockquote> + <p><span class="code">p.expect (pexpect.EOF)</span></p> + </blockquote> +</blockquote> +<hr noshade="noshade" size="1"> +<h1><a name="testing"></a>TESTING</h1> +<p>The following platforms have been tested:</p> +<!-- +<table class="pymenu" border="0" cellpadding="5"> + <tbody> + <tr> + <th class="pymenu">PLATFORM</th> + <th class="pymenu">RESULTS</th> + </tr> + <tr> + <td>Linux 2.4.9-ac10-rmk2-np1-cerf2<br> +armv4l</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Linux 2.4.18 #2<br> +sparc64</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>MacOS X Darwin Kernel Version 5.5<br> +powerpc</td> + <td> + <p>failed more than one test.</p> + <p>Generally Pexpect works on OS X, but the nature of the quirks +cause a many of the tests to fail. See <a href="#bugs">bugs</a> +(Incomplete Child Output). The problem is more than minor, but Pexpect +is still more than useful for most tasks. The problem is an edge case.</p> + </td> + </tr> + <tr> + <td>Linux 2.2.20<br> +alpha<br> + </td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Linux 2.4.18-5smp<br> +i686</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>OpenBSD 2.9 GENERIC#653<br> +i386</td> + <td><b><i>all tests passed</i></b></td> + </tr> + <tr> + <td>Solaris</td> + <td> + <p>failed <span class="code">test_destructor</span></p> + <p>Otherwise, this is working pretty well. The destructor problem +is minor. For some reason, the <i>second</i> time a pty file +descriptor is created and deleted it never gets returned for use. It +does not effect the first time or the third time or any time after +that. It's only the second time. This is weird... This could be a file +descriptor leak, or it could be some peculiarity of how Solaris +recycles them. I thought it was a UNIX requirement for the OS to give +you the lowest available filedescriptor number. In any case, this +should not be a problem unless you create hundreds of pexpect +instances... It may also be a pty module bug. </p> + </td> + </tr> + <tr> + <td>Windows XP Cygwin</td> + <td>failed <span class="code">test_destructor</span>. That it +works at all is amazing to me. Cygwin rules!</td> + </tr> + </tbody> +</table> +--> +<h1> </h1> +<h1><a name="todo">TO DO</a></h1> +<p>Add an option to add a delay after each expect() or before each +read()/readline() call to automatically avoid the <a href="#echo_bug">echo +bug</a>.</p> +<p> </p> +</div> +<hr noshade="noshade" size="1"> +<table border="0"> + <tbody> + <tr> + <td> <a href="http://www.noah.org/email/"><img src="email.png" + alt="Click to send email." border="0" height="16" width="100"></a> </td> + </tr> + </tbody> +</table> +</div> +<div id="Menu"><b>INDEX</b><br> +<hr noshade="noshade" size="1"> <a href="#license" + title="Python Software Foundation License">License</a><br> +<a href="#download" title="Download and setup instructions">Download</a><br> +<a href="#doc" title="Documentation and overview">Documentation</a><br> +<a href="#status" title="Project Status">Project Status</a><br> +<a href="#requirements" title="System requirements to use Pexpect">Requirements</a><br> +<a href="#overview" title="Overview of what Pexpect does">Overview</a><br> +<a href="#faq" title="FAQ">FAQ</a><br> +<a href="#bugs" title="Bugs and work-arounds">Known Bugs</a><br> +<a href="#changes" title="What's new with Pexpect">Recent Changes</a><br> +<a href="#testing" title="Test results on various platforms">Testing</a><br> +<a href="#todo" title="What to do next">To do</a><br> +<a href="http://pexpect.svn.sourceforge.net/viewvc/pexpect/trunk/pexpect/" title="browse SVN">Browse SVN</a><br> +<br> +<a href="http://sourceforge.net/projects/pexpect/" + title="The Pexpect project page on SourceForge.net"> <img + src="http://sourceforge.net/sflogo.php?group_id=59762&type=5" + alt="The Pexpect project page on SourceForge.net" border="0" + height="31" width="105"> </a> </div> +</body> +</html> diff --git a/third_party/Python/module/pexpect-2.4/examples/README b/third_party/Python/module/pexpect-2.4/examples/README new file mode 100644 index 000000000000..8f2581e05e7c --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/README @@ -0,0 +1,72 @@ +This directory contains scripts that give examples of using Pexpect. + +hive.py + This script creates SSH connections to a list of hosts that + you provide. Then you are given a command line prompt. Each + shell command that you enter is sent to all the hosts. The + response from each host is collected and printed. For example, + you could connect to a dozen different machines and reboot + them all at once. + +script.py + This implements a command similar to the classic BSD "script" command. + This will start a subshell and log all input and output to a file. + This demonstrates the interact() method of Pexpect. + +fix_cvs_files.py + This is for cleaning up binary files improperly added to + CVS. This script scans the given path to find binary files; + checks with CVS to see if the sticky options are set to -kb; + finally if sticky options are not -kb then uses 'cvs admin' + to set the -kb option. + +ftp.py + This demonstrates an FTP "bookmark". + This connects to an ftp site; does a few ftp commands; and then gives the user + interactive control over the session. In this case the "bookmark" is to a + directory on the OpenBSD ftp server. It puts you in the i386 packages + directory. You can easily modify this for other sites. + This demonstrates the interact() method of Pexpect. + +monitor.py + This runs a sequence of system status commands on a remote host using SSH. + It runs a simple system checks such as uptime and free to monitor + the state of the remote host. + +passmass.py + This will login to a list of hosts and change the password of the + given user. This demonstrates scripting logins; although, you could + more easily do this using the pxssh subclass of Pexpect. + See also the "hive.py" example script for a more general example + of scripting a collection of servers. + +python.py + This starts the python interpreter and prints the greeting message backwards. + It then gives the user interactive control of Python. It's pretty useless! + +rippy.py + This is a wizard for mencoder. It greatly simplifies the process of + ripping a DVD to mpeg4 format (XviD, DivX). It can transcode from any + video file to another. It has options for resampling the audio stream; + removing interlace artifacts, fitting to a target file size, etc. + There are lots of options, but the process is simple and easy to use. + +sshls.py + This lists a directory on a remote machine. + +ssh_tunnel.py + This starts an SSH tunnel to a remote machine. It monitors the connection + and restarts the tunnel if it goes down. + +uptime.py + This will run the uptime command and parse the output into python variables. + This demonstrates using a single regular expression to match the output + of a command and capturing different variable in match groups. + The regular expression takes into account a wide variety of different + formats for uptime output. + +df.py + This collects filesystem capacity info using the 'df' command. + Tuples of filesystem name and percentage are stored in a list. + A simple report is printed. Filesystems over 95% capacity are highlighted. + diff --git a/third_party/Python/module/pexpect-2.4/examples/astat.py b/third_party/Python/module/pexpect-2.4/examples/astat.py new file mode 100644 index 000000000000..82fa3c68b705 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/astat.py @@ -0,0 +1,74 @@ +#!/usr/bin/env python + +"""This runs Apache Status on the remote host and returns the number of requests per second. + +./astat.py [-s server_hostname] [-u username] [-p password] + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : Password to user for login. + +Example: + This will print information about the given host: + ./astat.py -s www.example.com -u mylogin -p mypassword + +""" + +import os, sys, time, re, getopt, getpass +import traceback +import pexpect, pxssh + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + ###################################################################### + ## Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if '-s' in options: + hostname = options['-s'] + else: + hostname = raw_input('hostname: ') + if '-u' in options: + username = options['-u'] + else: + username = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + + # + # Login via SSH + # + p = pxssh.pxssh() + p.login(hostname, username, password) + p.sendline('apachectl status') + p.expect('([0-9]+\.[0-9]+)\s*requests/sec') + requests_per_second = p.match.groups()[0] + p.logout() + print requests_per_second + +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/third_party/Python/module/pexpect-2.4/examples/bd_client.py b/third_party/Python/module/pexpect-2.4/examples/bd_client.py new file mode 100644 index 000000000000..564739a0aad5 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/bd_client.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python + +"""This is a very simple client for the backdoor daemon. This is intended more +for testing rather than normal use. See bd_serv.py """ + +import socket +import sys, time, select + +def recv_wrapper(s): + r,w,e = select.select([s.fileno()],[],[], 2) + if not r: + return '' + #cols = int(s.recv(4)) + #rows = int(s.recv(4)) + cols = 80 + rows = 24 + packet_size = cols * rows * 2 # double it for good measure + return s.recv(packet_size) + +#HOST = '' #'localhost' # The remote host +#PORT = 1664 # The same port as used by the server +s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) +s.connect(sys.argv[1])#(HOST, PORT)) +time.sleep(1) +#s.setblocking(0) +#s.send('COMMAND' + '\x01' + sys.argv[1]) +s.send(':sendline ' + sys.argv[2]) +print recv_wrapper(s) +s.close() +sys.exit() +#while True: +# data = recv_wrapper(s) +# if data == '': +# break +# sys.stdout.write (data) +# sys.stdout.flush() +#s.close() + diff --git a/third_party/Python/module/pexpect-2.4/examples/bd_serv.py b/third_party/Python/module/pexpect-2.4/examples/bd_serv.py new file mode 100644 index 000000000000..b7def9e14022 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/bd_serv.py @@ -0,0 +1,316 @@ +#!/usr/bin/env python + +"""Back door shell server + +This exposes an shell terminal on a socket. + + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request +""" + +# Having the password on the command line is not a good idea, but +# then this entire project is probably not the most security concious thing +# I've ever built. This should be considered an experimental tool -- at best. +import pxssh, pexpect, ANSI +import time, sys, os, getopt, getpass, traceback, threading, socket + +def exit_with_usage(exit_code=1): + + print globals()['__doc__'] + os._exit(exit_code) + +class roller (threading.Thread): + + """This runs a function in a loop in a thread.""" + + def __init__(self, interval, function, args=[], kwargs={}): + + """The interval parameter defines time between each call to the function. + """ + + threading.Thread.__init__(self) + self.interval = interval + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + + def cancel(self): + + """Stop the roller.""" + + self.finished.set() + + def run(self): + + while not self.finished.isSet(): + # self.finished.wait(self.interval) + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + + """This keeps the screen updated with the output of the child. This runs in + a separate thread. See roller(). """ + + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + #while True: + # #child.prompt (timeout=refresh_timeout) + # try: + # #child.read_nonblocking(1,timeout=refresh_timeout) + # child.read_nonblocking(4000, 0.1) + # except: + # pass + +def daemonize (stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): + + '''This forks the current process into a daemon. Almost none of this is + necessary (or advisable) if your daemon is being started by inetd. In that + case, stdin, stdout and stderr are all set up for you to refer to the + network connection, and the fork()s and session manipulation should not be + done (to avoid confusing inetd). Only the chdir() and umask() steps remain + as useful. + + References: + UNIX Programming FAQ + 1.7 How do I get my program to act like a daemon? + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 + + Advanced Programming in the Unix Environment + W. Richard Stevens, 1992, Addison-Wesley, ISBN 0-201-56317-7. + + The stdin, stdout, and stderr arguments are file names that will be opened + and be used to replace the standard file descriptors in sys.stdin, + sys.stdout, and sys.stderr. These arguments are optional and default to + /dev/null. Note that stderr is opened unbuffered, so if it shares a file + with stdout then interleaved output may not appear in the order that you + expect. ''' + + # Do first fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit first parent. + except OSError, e: + sys.stderr.write ("fork #1 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Decouple from parent environment. + os.chdir("/") + os.umask(0) + os.setsid() + + # Do second fork. + try: + pid = os.fork() + if pid > 0: + sys.exit(0) # Exit second parent. + except OSError, e: + sys.stderr.write ("fork #2 failed: (%d) %s\n" % (e.errno, e.strerror) ) + sys.exit(1) + + # Now I am a daemon! + + # Redirect standard file descriptors. + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + # I now return as the daemon + return 0 + +def add_cursor_blink (response, row, col): + + i = (row-1) * 80 + col + return response[:i]+'<img src="http://www.noah.org/cursor.gif">'+response[i:] + +def main (): + + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + print "Login for %s@%s:%s" % (username, hostname, port) + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + if daemon_mode: + print "daemonizing server" + daemonize() + #daemonize('/dev/null','/tmp/daemon.log','/tmp/daemon.log') + + sys.stdout.write ('server started with pid %d\n' % os.getpid() ) + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + child.login (hostname, username, password) + print 'created shell. command line prompt is', child.PROMPT + #child.sendline ('stty -echo') + #child.setecho(False) + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + localhost = '127.0.0.1' + s.bind('/tmp/mysock') + os.chmod('/tmp/mysock',0777) + print 'Listen' + s.listen(1) + print 'Accept' + #s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + #localhost = '127.0.0.1' + #s.bind((localhost, port)) + #print 'Listen' + #s.listen(1) + + r = roller (0.01, endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + print "screen poll updater started in background thread" + sys.stdout.flush() + + try: + while True: + conn, addr = s.accept() + print 'Connected by', addr + data = conn.recv(1024) + if data[0]!=':': + cmd = ':sendline' + arg = data.strip() + else: + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + if cmd == ':exit': + r.cancel() + break + elif cmd == ':sendline': + child.sendline (arg) + #child.prompt(timeout=2) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':send' or cmd==':xsend': + if cmd==':xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.2) + shell_window = str(virtual_screen) + elif cmd == ':cursor': + shell_window = '%x%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == ':refresh': + shell_window = str(virtual_screen) + + response = [] + response.append (shell_window) + #response = add_cursor_blink (response, row, col) + sent = conn.send('\n'.join(response)) + if watch_mode: print '\n'.join(response) + if sent < len (response): + print "Sent is too short. Some data was cut off." + conn.close() + finally: + r.cancel() + print "cleaning up socket" + s.close() + if os.path.exists("/tmp/mysock"): os.remove("/tmp/mysock") + print "done!" + +def pretty_box (rows, cols, s): + + """This puts an ASCII text box around the given string, s. + """ + + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def error_response (msg): + + response = [] + response.append ("""All commands start with : +:{REQUEST} {ARGUMENT} +{REQUEST} may be one of the following: + :sendline: Run the ARGUMENT followed by a line feed. + :send : send the characters in the ARGUMENT without a line feed. + :refresh : Use to catch up the screen with the shell if state gets out of sync. +Example: + :sendline ls -l +You may also leave off :command and it will be assumed. +Example: + ls -l +is equivalent to: + :sendline ls -l +""") + response.append (msg) + return '\n'.join(response) + +def parse_host_connect_string (hcs): + + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. """ + + if '@' in hcs: + p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + else: + p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +if __name__ == "__main__": + + try: + start_time = time.time() + print time.asctime() + main() + print time.asctime() + print "TOTAL TIME IN MINUTES:", + print (time.time() - start_time) / 60.0 + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) + diff --git a/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi b/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi new file mode 100644 index 000000000000..1e3affc1cab6 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/cgishell.cgi @@ -0,0 +1,762 @@ +#!/usr/bin/python +##!/usr/bin/env python +"""CGI shell server + +This exposes a shell terminal on a web page. +It uses AJAX to send keys and receive screen updates. +The client web browser needs nothing but CSS and Javascript. + + --hostname : sets the remote host name to open an ssh connection to. + --username : sets the user name to login with + --password : (optional) sets the password to login with + --port : set the local port for the server to listen on + --watch : show the virtual screen after each client request + +This project is probably not the most security concious thing I've ever built. +This should be considered an experimental tool -- at best. +""" +import sys,os +sys.path.insert (0,os.getcwd()) # let local modules precede any installed modules +import socket, random, string, traceback, cgi, time, getopt, getpass, threading, resource, signal +import pxssh, pexpect, ANSI + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + os._exit(exit_code) + +def client (command, host='localhost', port=-1): + """This sends a request to the server and returns the response. + If port <= 0 then host is assumed to be the filename of a Unix domain socket. + If port > 0 then host is an inet hostname. + """ + if port <= 0: + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.connect(host) + else: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.connect((host, port)) + s.send(command) + data = s.recv (2500) + s.close() + return data + +def server (hostname, username, password, socket_filename='/tmp/server_sock', daemon_mode = True, verbose=False): + """This starts and services requests from a client. + If daemon_mode is True then this forks off a separate daemon process and returns the daemon's pid. + If daemon_mode is False then this does not return until the server is done. + """ + if daemon_mode: + mypid_name = '/tmp/%d.pid' % os.getpid() + daemon_pid = daemonize(daemon_pid_filename=mypid_name) + time.sleep(1) + if daemon_pid != 0: + os.unlink(mypid_name) + return daemon_pid + + virtual_screen = ANSI.ANSI (24,80) + child = pxssh.pxssh() + try: + child.login (hostname, username, password, login_naked=True) + except: + return + if verbose: print 'login OK' + virtual_screen.write (child.before) + virtual_screen.write (child.after) + + if os.path.exists(socket_filename): os.remove(socket_filename) + s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + s.bind(socket_filename) + os.chmod(socket_filename, 0777) + if verbose: print 'Listen' + s.listen(1) + + r = roller (endless_poll, (child, child.PROMPT, virtual_screen)) + r.start() + if verbose: print "started screen-poll-updater in background thread" + sys.stdout.flush() + try: + while True: + conn, addr = s.accept() + if verbose: print 'Connected by', addr + data = conn.recv(1024) + request = data.split(' ', 1) + if len(request)>1: + cmd = request[0].strip() + arg = request[1].strip() + else: + cmd = request[0].strip() + arg = '' + + if cmd == 'exit': + r.cancel() + break + elif cmd == 'sendline': + child.sendline (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'send' or cmd=='xsend': + if cmd=='xsend': + arg = arg.decode("hex") + child.send (arg) + time.sleep(0.1) + shell_window = str(virtual_screen) + elif cmd == 'cursor': + shell_window = '%x,%x' % (virtual_screen.cur_r, virtual_screen.cur_c) + elif cmd == 'refresh': + shell_window = str(virtual_screen) + elif cmd == 'hash': + shell_window = str(hash(str(virtual_screen))) + + response = [] + response.append (shell_window) + if verbose: print '\n'.join(response) + sent = conn.send('\n'.join(response)) + if sent < len (response): + if verbose: print "Sent is too short. Some data was cut off." + conn.close() + except e: + pass + r.cancel() + if verbose: print "cleaning up socket" + s.close() + if os.path.exists(socket_filename): os.remove(socket_filename) + if verbose: print "server done!" + +class roller (threading.Thread): + """This class continuously loops a function in a thread. + This is basically a thin layer around Thread with a + while loop and a cancel. + """ + def __init__(self, function, args=[], kwargs={}): + threading.Thread.__init__(self) + self.function = function + self.args = args + self.kwargs = kwargs + self.finished = threading.Event() + def cancel(self): + """Stop the roller.""" + self.finished.set() + def run(self): + while not self.finished.isSet(): + self.function(*self.args, **self.kwargs) + +def endless_poll (child, prompt, screen, refresh_timeout=0.1): + """This keeps the screen updated with the output of the child. + This will be run in a separate thread. See roller class. + """ + #child.logfile_read = screen + try: + s = child.read_nonblocking(4000, 0.1) + screen.write(s) + except: + pass + +def daemonize (stdin=None, stdout=None, stderr=None, daemon_pid_filename=None): + """This runs the current process in the background as a daemon. + The arguments stdin, stdout, stderr allow you to set the filename that the daemon reads and writes to. + If they are set to None then all stdio for the daemon will be directed to /dev/null. + If daemon_pid_filename is set then the pid of the daemon will be written to it as plain text + and the pid will be returned. If daemon_pid_filename is None then this will return None. + """ + UMASK = 0 + WORKINGDIR = "/" + MAXFD = 1024 + + # The stdio file descriptors are redirected to /dev/null by default. + if hasattr(os, "devnull"): + DEVNULL = os.devnull + else: + DEVNULL = "/dev/null" + if stdin is None: stdin = DEVNULL + if stdout is None: stdout = DEVNULL + if stderr is None: stderr = DEVNULL + + try: + pid = os.fork() + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: # The first child. + os.waitpid(pid,0) + if daemon_pid_filename is not None: + daemon_pid = int(file(daemon_pid_filename,'r').read()) + return daemon_pid + else: + return None + + # first child + os.setsid() + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + try: + pid = os.fork() # fork second child + except OSError, e: + raise Exception, "%s [%d]" % (e.strerror, e.errno) + + if pid != 0: + if daemon_pid_filename is not None: + file(daemon_pid_filename,'w').write(str(pid)) + os._exit(0) # exit parent (the first child) of the second child. + + # second child + os.chdir(WORKINGDIR) + os.umask(UMASK) + + maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] + if maxfd == resource.RLIM_INFINITY: + maxfd = MAXFD + + # close all file descriptors + for fd in xrange(0, maxfd): + try: + os.close(fd) + except OSError: # fd wasn't open to begin with (ignored) + pass + + os.open (DEVNULL, os.O_RDWR) # standard input + + # redirect standard file descriptors + si = open(stdin, 'r') + so = open(stdout, 'a+') + se = open(stderr, 'a+', 0) + os.dup2(si.fileno(), sys.stdin.fileno()) + os.dup2(so.fileno(), sys.stdout.fileno()) + os.dup2(se.fileno(), sys.stderr.fileno()) + + return 0 + +def client_cgi (): + """This handles the request if this script was called as a cgi. + """ + sys.stderr = sys.stdout + ajax_mode = False + TITLE="Shell" + SHELL_OUTPUT="" + SID="NOT" + print "Content-type: text/html;charset=utf-8\r\n" + try: + form = cgi.FieldStorage() + if form.has_key('ajax'): + ajax_mode = True + ajax_cmd = form['ajax'].value + SID=form['sid'].value + if ajax_cmd == 'send': + command = 'xsend' + arg = form['arg'].value.encode('hex') + result = client (command + ' ' + arg, '/tmp/'+SID) + print result + elif ajax_cmd == 'refresh': + command = 'refresh' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'cursor': + command = 'cursor' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'exit': + command = 'exit' + result = client (command, '/tmp/'+SID) + print result + elif ajax_cmd == 'hash': + command = 'hash' + result = client (command, '/tmp/'+SID) + print result + elif not form.has_key('sid'): + SID=random_sid() + print LOGIN_HTML % locals(); + else: + SID=form['sid'].value + if form.has_key('start_server'): + USERNAME = form['username'].value + PASSWORD = form['password'].value + dpid = server ('127.0.0.1', USERNAME, PASSWORD, '/tmp/'+SID) + SHELL_OUTPUT="daemon pid: " + str(dpid) + else: + if form.has_key('cli'): + command = 'sendline ' + form['cli'].value + else: + command = 'sendline' + SHELL_OUTPUT = client (command, '/tmp/'+SID) + print CGISH_HTML % locals() + except: + tb_dump = traceback.format_exc() + if ajax_mode: + print str(tb_dump) + else: + SHELL_OUTPUT=str(tb_dump) + print CGISH_HTML % locals() + +def server_cli(): + """This is the command line interface to starting the server. + This handles things if the script was not called as a CGI + (if you run it from the command line). + """ + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?d', ['help','h','?', 'hostname=', 'username=', 'password=', 'port=', 'watch']) + except Exception, e: + print str(e) + exit_with_usage() + + command_line_options = dict(optlist) + options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + hostname = "127.0.0.1" + #port = 1664 + username = os.getenv('USER') + password = "" + daemon_mode = False + if '-d' in options: + daemon_mode = True + if '--watch' in options: + watch_mode = True + else: + watch_mode = False + if '--hostname' in options: + hostname = options['--hostname'] + if '--port' in options: + port = int(options['--port']) + if '--username' in options: + username = options['--username'] + if '--password' in options: + password = options['--password'] + else: + password = getpass.getpass('password: ') + + server (hostname, username, password, '/tmp/mysock', daemon_mode) + +def random_sid (): + a=random.randint(0,65535) + b=random.randint(0,65535) + return '%04x%04x.sid' % (a,b) + +def parse_host_connect_string (hcs): + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. + """ + if '@' in hcs: + p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + else: + p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +def pretty_box (s, rows=24, cols=80): + """This puts an ASCII text box around the given string. + """ + top_bot = '+' + '-'*cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in s.split('\n')]) + '\n' + top_bot + +def main (): + if os.getenv('REQUEST_METHOD') is None: + server_cli() + else: + client_cgi() + +# It's mostly HTML and Javascript from here on out. +CGISH_HTML="""<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> +<html> +<head> +<title>%(TITLE)s %(SID)s</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<style type=text/css> +a {color: #9f9; text-decoration: none} +a:hover {color: #0f0} +hr {color: #0f0} +html,body,textarea,input,form +{ +font-family: "Courier New", Courier, mono; +font-size: 8pt; +color: #0c0; +background-color: #020; +margin:0; +padding:0; +border:0; +} +input { background-color: #010; } +textarea { +border-width:1; +border-style:solid; +border-color:#0c0; +padding:3; +margin:3; +} +</style> + +<script language="JavaScript"> +function focus_first() +{if (document.forms.length > 0) +{var TForm = document.forms[0]; +for (i=0;i<TForm.length;i++){ +if ((TForm.elements[i].type=="text")|| +(TForm.elements[i].type=="textarea")|| +(TForm.elements[i].type.toString().charAt(0)=="s")) +{document.forms[0].elements[i].focus();break;}}}} + +// JavaScript Virtual Keyboard +// If you like this code then buy me a sandwich. +// Noah Spurrier <noah@noah.org> +var flag_shift=0; +var flag_shiftlock=0; +var flag_ctrl=0; +var ButtonOnColor="#ee0"; + +function init () +{ + // hack to set quote key to show both single quote and double quote + document.form['quote'].value = "'" + ' "'; + //refresh_screen(); + poll(); + document.form["cli"].focus(); +} +function get_password () +{ + var username = prompt("username?",""); + var password = prompt("password?",""); + start_server (username, password); +} +function multibrowser_ajax () +{ + var xmlHttp = false; +/*@cc_on @*/ +/*@if (@_jscript_version >= 5) + try + { + xmlHttp = new ActiveXObject("Msxml2.XMLHTTP"); + } + catch (e) + { + try + { + xmlHttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + catch (e2) + { + xmlHttp = false; + } + } +@end @*/ + + if (!xmlHttp && typeof XMLHttpRequest != 'undefined') + { + xmlHttp = new XMLHttpRequest(); + } + return xmlHttp; +} +function load_url_to_screen(url) +{ + xmlhttp = multibrowser_ajax(); + //window.XMLHttpRequest?new XMLHttpRequest(): new ActiveXObject("Microsoft.XMLHTTP"); + xmlhttp.onreadystatechange = update_virtual_screen; + xmlhttp.open("GET", url); + xmlhttp.setRequestHeader("If-Modified-Since", "Sat, 1 Jan 2000 00:00:00 GMT"); + xmlhttp.send(null); +} +function update_virtual_screen() +{ + if ((xmlhttp.readyState == 4) && (xmlhttp.status == 200)) + { + var screen_text = xmlhttp.responseText; + document.form["screen_text"].value = screen_text; + //var json_data = json_parse(xmlhttp.responseText); + } +} +function poll() +{ + refresh_screen(); + timerID = setTimeout("poll()", 2000); + // clearTimeout(timerID); +} +//function start_server (username, password) +//{ +// load_url_to_screen('cgishell.cgi?ajax=serverstart&username=' + escape(username) + '&password=' + escape(password); +//} +function refresh_screen() +{ + load_url_to_screen('cgishell.cgi?ajax=refresh&sid=%(SID)s'); +} +function query_hash() +{ + load_url_to_screen('cgishell.cgi?ajax=hash&sid=%(SID)s'); +} +function query_cursor() +{ + load_url_to_screen('cgishell.cgi?ajax=cursor&sid=%(SID)s'); +} +function exit_server() +{ + load_url_to_screen('cgishell.cgi?ajax=exit&sid=%(SID)s'); +} +function type_key (chars) +{ + var ch = '?'; + if (flag_shiftlock || flag_shift) + { + ch = chars.substr(1,1); + } + else if (flag_ctrl) + { + ch = chars.substr(2,1); + } + else + { + ch = chars.substr(0,1); + } + load_url_to_screen('cgishell.cgi?ajax=send&sid=%(SID)s&arg=' + escape(ch)); + if (flag_shift || flag_ctrl) + { + flag_shift = 0; + flag_ctrl = 0; + } + update_button_colors(); +} + +function key_shiftlock() +{ + flag_ctrl = 0; + flag_shift = 0; + if (flag_shiftlock) + { + flag_shiftlock = 0; + } + else + { + flag_shiftlock = 1; + } + update_button_colors(); +} + +function key_shift() +{ + if (flag_shift) + { + flag_shift = 0; + } + else + { + flag_ctrl = 0; + flag_shiftlock = 0; + flag_shift = 1; + } + update_button_colors(); +} +function key_ctrl () +{ + if (flag_ctrl) + { + flag_ctrl = 0; + } + else + { + flag_ctrl = 1; + flag_shiftlock = 0; + flag_shift = 0; + } + + update_button_colors(); +} +function update_button_colors () +{ + if (flag_ctrl) + { + document.form['Ctrl'].style.backgroundColor = ButtonOnColor; + document.form['Ctrl2'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['Ctrl'].style.backgroundColor = document.form.style.backgroundColor; + document.form['Ctrl2'].style.backgroundColor = document.form.style.backgroundColor; + } + if (flag_shift) + { + document.form['Shift'].style.backgroundColor = ButtonOnColor; + document.form['Shift2'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['Shift'].style.backgroundColor = document.form.style.backgroundColor; + document.form['Shift2'].style.backgroundColor = document.form.style.backgroundColor; + } + if (flag_shiftlock) + { + document.form['ShiftLock'].style.backgroundColor = ButtonOnColor; + } + else + { + document.form['ShiftLock'].style.backgroundColor = document.form.style.backgroundColor; + } + +} +function keyHandler(e) +{ + var pressedKey; + if (document.all) { e = window.event; } + if (document.layers) { pressedKey = e.which; } + if (document.all) { pressedKey = e.keyCode; } + pressedCharacter = String.fromCharCode(pressedKey); + type_key(pressedCharacter+pressedCharacter+pressedCharacter); + alert(pressedCharacter); +// alert(' Character = ' + pressedCharacter + ' [Decimal value = ' + pressedKey + ']'); +} +//document.onkeypress = keyHandler; +//if (document.layers) +// document.captureEvents(Event.KEYPRESS); +//http://sniptools.com/jskeys +//document.onkeyup = KeyCheck; +function KeyCheck(e) +{ + var KeyID = (window.event) ? event.keyCode : e.keyCode; + type_key(String.fromCharCode(KeyID)); + e.cancelBubble = true; + window.event.cancelBubble = true; +} +</script> + +</head> + +<body onload="init()"> +<form id="form" name="form" action="/cgi-bin/cgishell.cgi" method="POST"> +<input name="sid" value="%(SID)s" type="hidden"> +<textarea name="screen_text" cols="81" rows="25">%(SHELL_OUTPUT)s</textarea> +<hr noshade="1"> + <input name="cli" id="cli" type="text" size="80"><br> +<table border="0" align="left"> +<tr> +<td width="86%%" align="center"> + <input name="submit" type="submit" value="Submit"> + <input name="refresh" type="button" value="REFRESH" onclick="refresh_screen()"> + <input name="refresh" type="button" value="CURSOR" onclick="query_cursor()"> + <input name="hash" type="button" value="HASH" onclick="query_hash()"> + <input name="exit" type="button" value="EXIT" onclick="exit_server()"> + <br> + <input type="button" value="Esc" onclick="type_key('\\x1b\\x1b')" /> + <input type="button" value="` ~" onclick="type_key('`~')" /> + <input type="button" value="1!" onclick="type_key('1!')" /> + <input type="button" value="2@" onclick="type_key('2@\\x00')" /> + <input type="button" value="3#" onclick="type_key('3#')" /> + <input type="button" value="4$" onclick="type_key('4$')" /> + <input type="button" value="5%%" onclick="type_key('5%%')" /> + <input type="button" value="6^" onclick="type_key('6^\\x1E')" /> + <input type="button" value="7&" onclick="type_key('7&')" /> + <input type="button" value="8*" onclick="type_key('8*')" /> + <input type="button" value="9(" onclick="type_key('9(')" /> + <input type="button" value="0)" onclick="type_key('0)')" /> + <input type="button" value="-_" onclick="type_key('-_\\x1F')" /> + <input type="button" value="=+" onclick="type_key('=+')" /> + <input type="button" value="BkSp" onclick="type_key('\\x08\\x08\\x08')" /> + <br> + <input type="button" value="Tab" onclick="type_key('\\t\\t')" /> + <input type="button" value="Q" onclick="type_key('qQ\\x11')" /> + <input type="button" value="W" onclick="type_key('wW\\x17')" /> + <input type="button" value="E" onclick="type_key('eE\\x05')" /> + <input type="button" value="R" onclick="type_key('rR\\x12')" /> + <input type="button" value="T" onclick="type_key('tT\\x14')" /> + <input type="button" value="Y" onclick="type_key('yY\\x19')" /> + <input type="button" value="U" onclick="type_key('uU\\x15')" /> + <input type="button" value="I" onclick="type_key('iI\\x09')" /> + <input type="button" value="O" onclick="type_key('oO\\x0F')" /> + <input type="button" value="P" onclick="type_key('pP\\x10')" /> + <input type="button" value="[ {" onclick="type_key('[{\\x1b')" /> + <input type="button" value="] }" onclick="type_key(']}\\x1d')" /> + <input type="button" value="\\ |" onclick="type_key('\\\\|\\x1c')" /> + <br> + <input type="button" id="Ctrl" value="Ctrl" onclick="key_ctrl()" /> + <input type="button" value="A" onclick="type_key('aA\\x01')" /> + <input type="button" value="S" onclick="type_key('sS\\x13')" /> + <input type="button" value="D" onclick="type_key('dD\\x04')" /> + <input type="button" value="F" onclick="type_key('fF\\x06')" /> + <input type="button" value="G" onclick="type_key('gG\\x07')" /> + <input type="button" value="H" onclick="type_key('hH\\x08')" /> + <input type="button" value="J" onclick="type_key('jJ\\x0A')" /> + <input type="button" value="K" onclick="type_key('kK\\x0B')" /> + <input type="button" value="L" onclick="type_key('lL\\x0C')" /> + <input type="button" value="; :" onclick="type_key(';:')" /> + <input type="button" id="quote" value="'" onclick="type_key('\\x27\\x22')" /> + <input type="button" value="Enter" onclick="type_key('\\n\\n')" /> + <br> + <input type="button" id="ShiftLock" value="Caps Lock" onclick="key_shiftlock()" /> + <input type="button" id="Shift" value="Shift" onclick="key_shift()" /> + <input type="button" value="Z" onclick="type_key('zZ\\x1A')" /> + <input type="button" value="X" onclick="type_key('xX\\x18')" /> + <input type="button" value="C" onclick="type_key('cC\\x03')" /> + <input type="button" value="V" onclick="type_key('vV\\x16')" /> + <input type="button" value="B" onclick="type_key('bB\\x02')" /> + <input type="button" value="N" onclick="type_key('nN\\x0E')" /> + <input type="button" value="M" onclick="type_key('mM\\x0D')" /> + <input type="button" value=", <" onclick="type_key(',<')" /> + <input type="button" value=". >" onclick="type_key('.>')" /> + <input type="button" value="/ ?" onclick="type_key('/?')" /> + <input type="button" id="Shift2" value="Shift" onclick="key_shift()" /> + <input type="button" id="Ctrl2" value="Ctrl" onclick="key_ctrl()" /> + <br> + <input type="button" value=" FINAL FRONTIER " onclick="type_key(' ')" /> +</td> +</tr> +</table> +</form> +</body> +</html> +""" + +LOGIN_HTML="""<html> +<head> +<title>Shell Login</title> +<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1"> +<style type=text/css> +a {color: #9f9; text-decoration: none} +a:hover {color: #0f0} +hr {color: #0f0} +html,body,textarea,input,form +{ +font-family: "Courier New", Courier, mono; +font-size: 8pt; +color: #0c0; +background-color: #020; +margin:3; +padding:0; +border:0; +} +input { background-color: #010; } +input,textarea { +border-width:1; +border-style:solid; +border-color:#0c0; +padding:3; +margin:3; +} +</style> +<script language="JavaScript"> +function init () +{ + document.login_form["username"].focus(); +} +</script> +</head> +<body onload="init()"> +<form name="login_form" method="POST"> +<input name="start_server" value="1" type="hidden"> +<input name="sid" value="%(SID)s" type="hidden"> +username: <input name="username" type="text" size="30"><br> +password: <input name="password" type="password" size="30"><br> +<input name="submit" type="submit" value="enter"> +</form> +<br> +</body> +</html> +""" + +if __name__ == "__main__": + try: + main() + except Exception, e: + print str(e) + tb_dump = traceback.format_exc() + print str(tb_dump) + diff --git a/third_party/Python/module/pexpect-2.4/examples/chess.py b/third_party/Python/module/pexpect-2.4/examples/chess.py new file mode 100644 index 000000000000..8c32cf798f2a --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/chess.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI + +REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' +REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + + self.child.expect ('Chess') + if self.child.after != 'Chess': + raise IOError, 'incompatible chess program' + self.term.process_list (self.before) + self.term.process_list (self.after) + self.last_computer_move = '' + def read_until_cursor (self, r,c) + while 1: + self.child.read(1, 60) + self.term.process (c) + if self.term.cur_r == r and self.term.cur_c == c: + return 1 + + def do_first_move (self, move): + self.child.expect ('Your move is') + self.child.sendline (move) + self.term.process_list (self.before) + self.term.process_list (self.after) + return move + + def do_move (self, move): + read_until_cursor (19,60) + #self.child.expect ('\[19;60H') + self.child.sendline (move) + print 'do_move' move + return move + + def get_first_computer_move (self): + self.child.expect ('My move is') + self.child.expect (REGEX_MOVE) +# print '', self.child.after + return self.child.after + + def get_computer_move (self): + print 'Here' + i = self.child.expect (['\[17;59H', '\[17;58H']) + print i + if i == 0: + self.child.expect (REGEX_MOVE) + if len(self.child.after) < 4: + self.child.after = self.child.after + self.last_computer_move[3] + if i == 1: + self.child.expect (REGEX_MOVE_PART) + self.child.after = self.last_computer_move[0] + self.child.after + print '', self.child.after + self.last_computer_move = self.child.after + return self.child.after + + def switch (self): + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') +import sys, os +print 'Starting...' +white = Chess() +white.child.echo = 1 +white.child.expect ('Your move is') +white.set_depth(2) +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +white.do_move ('e7e5') +move_white = white.get_computer_move() +print 'move white:', move_white +white.do_move ('f8c5') +move_white = white.get_computer_move() +print 'move white:', move_white +white.do_move ('b8a6') +move_white = white.get_computer_move() +print 'move white:', move_white + +sys.exit(1) + + + +black = Chess() +white = Chess() +white.child.expect ('Your move is') +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +black.do_first_move (move_white) +move_black = black.get_first_computer_move() +print 'first move black:', move_black + +white.do_move (move_black) + +done = 0 +while not done: + move_white = white.get_computer_move() + print 'move white:', move_white + + black.do_move (move_white) + move_black = black.get_computer_move() + print 'move black:', move_black + + white.do_move (move_black) + print 'tail of loop' + +g.quit() + + diff --git a/third_party/Python/module/pexpect-2.4/examples/chess2.py b/third_party/Python/module/pexpect-2.4/examples/chess2.py new file mode 100644 index 000000000000..c62d5ce37a06 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/chess2.py @@ -0,0 +1,131 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI +import sys, os, time + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + + #self.child.expect ('Chess') + #if self.child.after != 'Chess': + # raise IOError, 'incompatible chess program' + #self.term.process_list (self.child.before) + #self.term.process_list (self.child.after) + + self.last_computer_move = '' + + def read_until_cursor (self, r,c, e=0): + '''Eventually something like this should move into the screen class or + a subclass. Maybe a combination of pexpect and screen... + ''' + fout = open ('log','a') + while self.term.cur_r != r or self.term.cur_c != c: + try: + k = self.child.read(1, 10) + except Exception, e: + print 'EXCEPTION, (r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c) + sys.stdout.flush() + self.term.process (k) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + if e: + sys.stdout.write (k) + sys.stdout.flush() + if self.term.cur_r == r and self.term.cur_c == c: + fout.close() + return 1 + print 'DIDNT EVEN HIT.' + fout.close() + return 1 + + def expect_region (self): + '''This is another method that would be moved into the + screen class. + ''' + pass + def do_scan (self): + fout = open ('log','a') + while 1: + c = self.child.read(1,10) + self.term.process (c) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + sys.stdout.write (c) + sys.stdout.flush() + + def do_move (self, move, e = 0): + time.sleep(1) + self.read_until_cursor (19,60, e) + self.child.sendline (move) + + def wait (self, color): + while 1: + r = self.term.get_region (14,50,14,60)[0] + r = r.strip() + if r == color: + return + time.sleep (1) + + def parse_computer_move (self, s): + i = s.find ('is: ') + cm = s[i+3:i+9] + return cm + def get_computer_move (self, e = 0): + time.sleep(1) + self.read_until_cursor (19,60, e) + time.sleep(1) + r = self.term.get_region (17,50,17,62)[0] + cm = self.parse_computer_move (r) + return cm + + def switch (self): + print 'switching' + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') + +def LOG (s): + print s + sys.stdout.flush () + fout = open ('moves.log', 'a') + fout.write (s + '\n') + fout.close() + +print 'Starting...' + +black = Chess() +white = Chess() +white.read_until_cursor (19,60,1) +white.switch() + +done = 0 +while not done: + white.wait ('Black') + move_white = white.get_computer_move(1) + LOG ( 'move white:'+ move_white ) + + black.do_move (move_white) + black.wait ('White') + move_black = black.get_computer_move() + LOG ( 'move black:'+ move_black ) + + white.do_move (move_black, 1) + +g.quit() + + diff --git a/third_party/Python/module/pexpect-2.4/examples/chess3.py b/third_party/Python/module/pexpect-2.4/examples/chess3.py new file mode 100644 index 000000000000..44044420a1c9 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/chess3.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python + +'''This demonstrates controlling a screen oriented application (curses). +It starts two instances of gnuchess and then pits them against each other. +''' + +import pexpect +import string +import ANSI + +REGEX_MOVE = '(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' +REGEX_MOVE_PART = '(?:[0-9]|\x1b\[C)(?:[a-z]|\x1b\[C)(?:[0-9]|\x1b\[C)' + +class Chess: + + def __init__(self, engine = "/usr/local/bin/gnuchess -a -h 1"): + self.child = pexpect.spawn (engine) + self.term = ANSI.ANSI () + +# self.child.expect ('Chess') + # if self.child.after != 'Chess': + # raise IOError, 'incompatible chess program' + # self.term.process_list (self.before) + # self.term.process_list (self.after) + self.last_computer_move = '' + def read_until_cursor (self, r,c): + fout = open ('log','a') + while 1: + k = self.child.read(1, 10) + self.term.process (k) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + if self.term.cur_r == r and self.term.cur_c == c: + fout.close() + return 1 + sys.stdout.write (k) + sys.stdout.flush() + + def do_scan (self): + fout = open ('log','a') + while 1: + c = self.child.read(1,10) + self.term.process (c) + fout.write ('(r,c):(%d,%d)\n' %(self.term.cur_r, self.term.cur_c)) + fout.flush() + sys.stdout.write (c) + sys.stdout.flush() + + def do_move (self, move): + self.read_until_cursor (19,60) + self.child.sendline (move) + return move + + def get_computer_move (self): + print 'Here' + i = self.child.expect (['\[17;59H', '\[17;58H']) + print i + if i == 0: + self.child.expect (REGEX_MOVE) + if len(self.child.after) < 4: + self.child.after = self.child.after + self.last_computer_move[3] + if i == 1: + self.child.expect (REGEX_MOVE_PART) + self.child.after = self.last_computer_move[0] + self.child.after + print '', self.child.after + self.last_computer_move = self.child.after + return self.child.after + + def switch (self): + self.child.sendline ('switch') + + def set_depth (self, depth): + self.child.sendline ('depth') + self.child.expect ('depth=') + self.child.sendline ('%d' % depth) + + def quit(self): + self.child.sendline ('quit') +import sys, os +print 'Starting...' +white = Chess() +white.do_move('b2b4') +white.read_until_cursor (19,60) +c1 = white.term.get_abs(17,58) +c2 = white.term.get_abs(17,59) +c3 = white.term.get_abs(17,60) +c4 = white.term.get_abs(17,61) +fout = open ('log','a') +fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4)) +fout.close() +white.do_move('c2c4') +white.read_until_cursor (19,60) +c1 = white.term.get_abs(17,58) +c2 = white.term.get_abs(17,59) +c3 = white.term.get_abs(17,60) +c4 = white.term.get_abs(17,61) +fout = open ('log','a') +fout.write ('Computer:%s%s%s%s\n' %(c1,c2,c3,c4)) +fout.close() +white.do_scan () + +#white.do_move ('b8a6') +#move_white = white.get_computer_move() +#print 'move white:', move_white + +sys.exit(1) + + + +black = Chess() +white = Chess() +white.child.expect ('Your move is') +white.switch() + +move_white = white.get_first_computer_move() +print 'first move white:', move_white + +black.do_first_move (move_white) +move_black = black.get_first_computer_move() +print 'first move black:', move_black + +white.do_move (move_black) + +done = 0 +while not done: + move_white = white.get_computer_move() + print 'move white:', move_white + + black.do_move (move_white) + move_black = black.get_computer_move() + print 'move black:', move_black + + white.do_move (move_black) + print 'tail of loop' + +g.quit() + + diff --git a/third_party/Python/module/pexpect-2.4/examples/df.py b/third_party/Python/module/pexpect-2.4/examples/df.py new file mode 100644 index 000000000000..64bbf93a1b29 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/df.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python + +"""This collects filesystem capacity info using the 'df' command. Tuples of +filesystem name and percentage are stored in a list. A simple report is +printed. Filesystems over 95% capacity are highlighted. Note that this does not +parse filesystem names after the first space, so names with spaces in them will +be truncated. This will produce ambiguous results for automount filesystems on +Apple OSX. """ + +import pexpect + +child = pexpect.spawn ('df') + +# parse 'df' output into a list. +pattern = "\n(\S+).*?([0-9]+)%" +filesystem_list = [] +for dummy in range (0, 1000): + i = child.expect ([pattern, pexpect.EOF]) + if i == 0: + filesystem_list.append (child.match.groups()) + else: + break + +# Print report +print +for m in filesystem_list: + s = "Filesystem %s is at %s%%" % (m[0], m[1]) + # highlight filesystems over 95% capacity + if int(m[1]) > 95: + s = '! ' + s + else: + s = ' ' + s + print s + diff --git a/third_party/Python/module/pexpect-2.4/examples/fix_cvs_files.py b/third_party/Python/module/pexpect-2.4/examples/fix_cvs_files.py new file mode 100644 index 000000000000..e75a1496abdd --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/fix_cvs_files.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python + +"""This is for cleaning up binary files improperly added to CVS. This script +scans the given path to find binary files; checks with CVS to see if the sticky +options are set to -kb; finally if sticky options are not -kb then uses 'cvs +admin' to set the -kb option. + +This script ignores CVS directories, symbolic links, and files not known under +CVS control (cvs status is 'Unknown'). + +Run this on a CHECKED OUT module sandbox, not on the repository itself. After +if fixes the sticky options on any files you should manually do a 'cvs commit' +to accept the changes. Then be sure to have all users do a 'cvs up -A' to +update the Sticky Option status. + +Noah Spurrier +20030426 +""" + +import os, sys, time +import pexpect + +VERBOSE = 1 + +def is_binary (filename): + + """Assume that any file with a character where the 8th bit is set is + binary. """ + + fin = open(filename, 'rb') + wholething = fin.read() + fin.close() + for c in wholething: + if ord(c) & 0x80: + return 1 + return 0 + +def is_kb_sticky (filename): + + """This checks if 'cvs status' reports '-kb' for Sticky options. If the + Sticky Option status is '-ks' then this returns 1. If the status is + 'Unknown' then it returns 1. Otherwise 0 is returned. """ + + try: + s = pexpect.spawn ('cvs status %s' % filename) + i = s.expect (['Sticky Options:\s*(.*)\r\n', 'Status: Unknown']) + if i==1 and VERBOSE: + print 'File not part of CVS repository:', filename + return 1 # Pretend it's OK. + if s.match.group(1) == '-kb': + return 1 + s = None + except: + print 'Something went wrong trying to run external cvs command.' + print ' cvs status %s' % filename + print 'The cvs command returned:' + print s.before + return 0 + +def cvs_admin_kb (filename): + + """This uses 'cvs admin' to set the '-kb' sticky option. """ + + s = pexpect.run ('cvs admin -kb %s' % filename) + # There is a timing issue. If I run 'cvs admin' too quickly + # cvs sometimes has trouble obtaining the directory lock. + time.sleep(1) + +def walk_and_clean_cvs_binaries (arg, dirname, names): + + """This contains the logic for processing files. This is the os.path.walk + callback. This skips dirnames that end in CVS. """ + + if len(dirname)>3 and dirname[-3:]=='CVS': + return + for n in names: + fullpath = os.path.join (dirname, n) + if os.path.isdir(fullpath) or os.path.islink(fullpath): + continue + if is_binary(fullpath): + if not is_kb_sticky (fullpath): + if VERBOSE: print fullpath + cvs_admin_kb (fullpath) + +def main (): + + if len(sys.argv) == 1: + root = '.' + else: + root = sys.argv[1] + os.path.walk (root, walk_and_clean_cvs_binaries, None) + +if __name__ == '__main__': + main () + diff --git a/third_party/Python/module/pexpect-2.4/examples/ftp.py b/third_party/Python/module/pexpect-2.4/examples/ftp.py new file mode 100644 index 000000000000..89a502e1b8f4 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/ftp.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +"""This demonstrates an FTP "bookmark". This connects to an ftp site; does a +few ftp stuff; and then gives the user interactive control over the session. In +this case the "bookmark" is to a directory on the OpenBSD ftp server. It puts +you in the i386 packages directory. You can easily modify this for other sites. +""" + +import pexpect +import sys + +child = pexpect.spawn('ftp ftp.openbsd.org') +child.expect('(?i)name .*: ') +child.sendline('anonymous') +child.expect('(?i)password') +child.sendline('pexpect@sourceforge.net') +child.expect('ftp> ') +child.sendline('cd /pub/OpenBSD/3.7/packages/i386') +child.expect('ftp> ') +child.sendline('bin') +child.expect('ftp> ') +child.sendline('prompt') +child.expect('ftp> ') +child.sendline('pwd') +child.expect('ftp> ') +print("Escape character is '^]'.\n") +sys.stdout.write (child.after) +sys.stdout.flush() +child.interact() # Escape character defaults to ^] +# At this point this script blocks until the user presses the escape character +# or until the child exits. The human user and the child should be talking +# to each other now. + +# At this point the script is running again. +print 'Left interactve mode.' + +# The rest is not strictly necessary. This just demonstrates a few functions. +# This makes sure the child is dead; although it would be killed when Python exits. +if child.isalive(): + child.sendline('bye') # Try to ask ftp child to exit. + child.close() +# Print the final state of the child. Normally isalive() should be FALSE. +if child.isalive(): + print 'Child did not exit gracefully.' +else: + print 'Child exited gracefully.' + diff --git a/third_party/Python/module/pexpect-2.4/examples/hive.py b/third_party/Python/module/pexpect-2.4/examples/hive.py new file mode 100644 index 000000000000..fcb75bcac67f --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/hive.py @@ -0,0 +1,437 @@ +#!/usr/bin/env python + +"""hive -- Hive Shell + +This lets you ssh to a group of servers and control them as if they were one. +Each command you enter is sent to each host in parallel. The response of each +host is collected and printed. In normal synchronous mode Hive will wait for +each host to return the shell command line prompt. The shell prompt is used to +sync output. + +Example: + + $ hive.py --sameuser --samepass host1.example.com host2.example.net + username: myusername + password: + connecting to host1.example.com - OK + connecting to host2.example.net - OK + targetting hosts: 192.168.1.104 192.168.1.107 + CMD (? for help) > uptime + ======================================================================= + host1.example.com + ----------------------------------------------------------------------- + uptime + 23:49:55 up 74 days, 5:14, 2 users, load average: 0.15, 0.05, 0.01 + ======================================================================= + host2.example.net + ----------------------------------------------------------------------- + uptime + 23:53:02 up 1 day, 13:36, 2 users, load average: 0.50, 0.40, 0.46 + ======================================================================= + +Other Usage Examples: + +1. You will be asked for your username and password for each host. + + hive.py host1 host2 host3 ... hostN + +2. You will be asked once for your username and password. + This will be used for each host. + + hive.py --sameuser --samepass host1 host2 host3 ... hostN + +3. Give a username and password on the command-line: + + hive.py user1:pass2@host1 user2:pass2@host2 ... userN:passN@hostN + +You can use an extended host notation to specify username, password, and host +instead of entering auth information interactively. Where you would enter a +host name use this format: + + username:password@host + +This assumes that ':' is not part of the password. If your password contains a +':' then you can use '\\:' to indicate a ':' and '\\\\' to indicate a single +'\\'. Remember that this information will appear in the process listing. Anyone +on your machine can see this auth information. This is not secure. + +This is a crude script that begs to be multithreaded. But it serves its +purpose. + +Noah Spurrier + +$Id: hive.py 509 2008-01-05 21:27:47Z noah $ +""" + +# TODO add feature to support username:password@host combination +# TODO add feature to log each host output in separate file + +import sys, os, re, optparse, traceback, types, time, getpass +import pexpect, pxssh +import readline, atexit + +#histfile = os.path.join(os.environ["HOME"], ".hive_history") +#try: +# readline.read_history_file(histfile) +#except IOError: +# pass +#atexit.register(readline.write_history_file, histfile) + +CMD_HELP="""Hive commands are preceded by a colon : (just think of vi). + +:target name1 name2 name3 ... + + set list of hosts to target commands + +:target all + + reset list of hosts to target all hosts in the hive. + +:to name command + + send a command line to the named host. This is similar to :target, but + sends only one command and does not change the list of targets for future + commands. + +:sync + + set mode to wait for shell prompts after commands are run. This is the + default. When Hive first logs into a host it sets a special shell prompt + pattern that it can later look for to synchronize output of the hosts. If + you 'su' to another user then it can upset the synchronization. If you need + to run something like 'su' then use the following pattern: + + CMD (? for help) > :async + CMD (? for help) > sudo su - root + CMD (? for help) > :prompt + CMD (? for help) > :sync + +:async + + set mode to not expect command line prompts (see :sync). Afterwards + commands are send to target hosts, but their responses are not read back + until :sync is run. This is useful to run before commands that will not + return with the special shell prompt pattern that Hive uses to synchronize. + +:refresh + + refresh the display. This shows the last few lines of output from all hosts. + This is similar to resync, but does not expect the promt. This is useful + for seeing what hosts are doing during long running commands. + +:resync + + This is similar to :sync, but it does not change the mode. It looks for the + prompt and thus consumes all input from all targetted hosts. + +:prompt + + force each host to reset command line prompt to the special pattern used to + synchronize all the hosts. This is useful if you 'su' to a different user + where Hive would not know the prompt to match. + +:send my text + + This will send the 'my text' wihtout a line feed to the targetted hosts. + This output of the hosts is not automatically synchronized. + +:control X + + This will send the given control character to the targetted hosts. + For example, ":control c" will send ASCII 3. + +:exit + + This will exit the hive shell. + +""" + +def login (args, cli_username=None, cli_password=None): + + # I have to keep a separate list of host names because Python dicts are not ordered. + # I want to keep the same order as in the args list. + host_names = [] + hive_connect_info = {} + hive = {} + # build up the list of connection information (hostname, username, password, port) + for host_connect_string in args: + hcd = parse_host_connect_string (host_connect_string) + hostname = hcd['hostname'] + port = hcd['port'] + if port == '': + port = None + if len(hcd['username']) > 0: + username = hcd['username'] + elif cli_username is not None: + username = cli_username + else: + username = raw_input('%s username: ' % hostname) + if len(hcd['password']) > 0: + password = hcd['password'] + elif cli_password is not None: + password = cli_password + else: + password = getpass.getpass('%s password: ' % hostname) + host_names.append(hostname) + hive_connect_info[hostname] = (hostname, username, password, port) + # build up the list of hive connections using the connection information. + for hostname in host_names: + print 'connecting to', hostname + try: + fout = file("log_"+hostname, "w") + hive[hostname] = pxssh.pxssh() + hive[hostname].login(*hive_connect_info[hostname]) + print hive[hostname].before + hive[hostname].logfile = fout + print '- OK' + except Exception, e: + print '- ERROR', + print str(e) + print 'Skipping', hostname + hive[hostname] = None + return host_names, hive + +def main (): + + global options, args, CMD_HELP + + if options.sameuser: + cli_username = raw_input('username: ') + else: + cli_username = None + + if options.samepass: + cli_password = getpass.getpass('password: ') + else: + cli_password = None + + host_names, hive = login(args, cli_username, cli_password) + + synchronous_mode = True + target_hostnames = host_names[:] + print 'targetting hosts:', ' '.join(target_hostnames) + while True: + cmd = raw_input('CMD (? for help) > ') + cmd = cmd.strip() + if cmd=='?' or cmd==':help' or cmd==':h': + print CMD_HELP + continue + elif cmd==':refresh': + refresh (hive, target_hostnames, timeout=0.5) + for hostname in target_hostnames: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + print '==============================================================================' + continue + elif cmd==':resync': + resync (hive, target_hostnames, timeout=0.5) + for hostname in target_hostnames: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + print '==============================================================================' + continue + elif cmd==':sync': + synchronous_mode = True + resync (hive, target_hostnames, timeout=0.5) + continue + elif cmd==':async': + synchronous_mode = False + continue + elif cmd==':prompt': + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].set_unique_prompt() + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:5] == ':send': + cmd, txt = cmd.split(None,1) + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].send(txt) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:3] == ':to': + cmd, hostname, txt = cmd.split(None,2) + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + continue + try: + hive[hostname].sendline (txt) + hive[hostname].prompt(timeout=2) + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:7] == ':expect': + cmd, pattern = cmd.split(None,1) + print 'looking for', pattern + try: + for hostname in target_hostnames: + if hive[hostname] is not None: + hive[hostname].expect(pattern) + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd[:7] == ':target': + target_hostnames = cmd.split()[1:] + if len(target_hostnames) == 0 or target_hostnames[0] == all: + target_hostnames = host_names[:] + print 'targetting hosts:', ' '.join(target_hostnames) + continue + elif cmd == ':exit' or cmd == ':q' or cmd == ':quit': + break + elif cmd[:8] == ':control' or cmd[:5] == ':ctrl' : + cmd, c = cmd.split(None,1) + if ord(c)-96 < 0 or ord(c)-96 > 255: + print '/=============================================================================' + print '| Invalid character. Must be [a-zA-Z], @, [, ], \\, ^, _, or ?' + print '\\-----------------------------------------------------------------------------' + continue + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].sendcontrol(c) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + continue + elif cmd == ':esc': + for hostname in target_hostnames: + if hive[hostname] is not None: + hive[hostname].send(chr(27)) + continue + # + # Run the command on all targets in parallel + # + for hostname in target_hostnames: + try: + if hive[hostname] is not None: + hive[hostname].sendline (cmd) + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + + # + # print the response for each targeted host. + # + if synchronous_mode: + for hostname in target_hostnames: + try: + if hive[hostname] is None: + print '/=============================================================================' + print '| ' + hostname + ' is DEAD' + print '\\-----------------------------------------------------------------------------' + else: + hive[hostname].prompt(timeout=2) + print '/=============================================================================' + print '| ' + hostname + print '\\-----------------------------------------------------------------------------' + print hive[hostname].before + except Exception, e: + print "Had trouble communicating with %s, so removing it from the target list." % hostname + print str(e) + hive[hostname] = None + print '==============================================================================' + +def refresh (hive, hive_names, timeout=0.5): + + """This waits for the TIMEOUT on each host. + """ + + # TODO This is ideal for threading. + for hostname in hive_names: + hive[hostname].expect([pexpect.TIMEOUT,pexpect.EOF],timeout=timeout) + +def resync (hive, hive_names, timeout=2, max_attempts=5): + + """This waits for the shell prompt for each host in an effort to try to get + them all to the same state. The timeout is set low so that hosts that are + already at the prompt will not slow things down too much. If a prompt match + is made for a hosts then keep asking until it stops matching. This is a + best effort to consume all input if it printed more than one prompt. It's + kind of kludgy. Note that this will always introduce a delay equal to the + timeout for each machine. So for 10 machines with a 2 second delay you will + get AT LEAST a 20 second delay if not more. """ + + # TODO This is ideal for threading. + for hostname in hive_names: + for attempts in xrange(0, max_attempts): + if not hive[hostname].prompt(timeout=timeout): + break + +def parse_host_connect_string (hcs): + + """This parses a host connection string in the form + username:password@hostname:port. All fields are options expcet hostname. A + dictionary is returned with all four keys. Keys that were not included are + set to empty strings ''. Note that if your password has the '@' character + then you must backslash escape it. """ + + if '@' in hcs: + p = re.compile (r'(?P<username>[^@:]*)(:?)(?P<password>.*)(?!\\)@(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + else: + p = re.compile (r'(?P<username>)(?P<password>)(?P<hostname>[^:]*):?(?P<port>[0-9]*)') + m = p.search (hcs) + d = m.groupdict() + d['password'] = d['password'].replace('\\@','@') + return d + +if __name__ == '__main__': + try: + start_time = time.time() + parser = optparse.OptionParser(formatter=optparse.TitledHelpFormatter(), usage=globals()['__doc__'], version='$Id: hive.py 509 2008-01-05 21:27:47Z noah $',conflict_handler="resolve") + parser.add_option ('-v', '--verbose', action='store_true', default=False, help='verbose output') + parser.add_option ('--samepass', action='store_true', default=False, help='Use same password for each login.') + parser.add_option ('--sameuser', action='store_true', default=False, help='Use same username for each login.') + (options, args) = parser.parse_args() + if len(args) < 1: + parser.error ('missing argument') + if options.verbose: print time.asctime() + main() + if options.verbose: print time.asctime() + if options.verbose: print 'TOTAL TIME IN MINUTES:', + if options.verbose: print (time.time() - start_time) / 60.0 + sys.exit(0) + except KeyboardInterrupt, e: # Ctrl-C + raise e + except SystemExit, e: # sys.exit() + raise e + except Exception, e: + print 'ERROR, UNEXPECTED EXCEPTION' + print str(e) + traceback.print_exc() + os._exit(1) diff --git a/third_party/Python/module/pexpect-2.4/examples/monitor.py b/third_party/Python/module/pexpect-2.4/examples/monitor.py new file mode 100644 index 000000000000..e31b51b6d210 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/monitor.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python + +""" This runs a sequence of commands on a remote host using SSH. It runs a +simple system checks such as uptime and free to monitor the state of the remote +host. + +./monitor.py [-s server_hostname] [-u username] [-p password] + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : Password to user for login. + +Example: + This will print information about the given host: + ./monitor.py -s www.example.com -u mylogin -p mypassword + +It works like this: + Login via SSH (This is the hardest part). + Run and parse 'uptime'. + Run 'iostat'. + Run 'vmstat'. + Run 'netstat' + Run 'free'. + Exit the remote host. +""" + +import os, sys, time, re, getopt, getpass +import traceback +import pexpect + +# +# Some constants. +# +COMMAND_PROMPT = '[#$] ' ### This is way too simple for industrial use -- we will change is ASAP. +TERMINAL_PROMPT = '(?i)terminal type\?' +TERMINAL_TYPE = 'vt100' +# This is the prompt we get if SSH does not have the remote host's public key stored in the cache. +SSH_NEWKEY = '(?i)are you sure you want to continue connecting' + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + global COMMAND_PROMPT, TERMINAL_PROMPT, TERMINAL_TYPE, SSH_NEWKEY + ###################################################################### + ## Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?s:u:p:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if '-s' in options: + host = options['-s'] + else: + host = raw_input('hostname: ') + if '-u' in options: + user = options['-u'] + else: + user = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + + # + # Login via SSH + # + child = pexpect.spawn('ssh -l %s %s'%(user, host)) + i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, COMMAND_PROMPT, '(?i)password']) + if i == 0: # Timeout + print 'ERROR! could not login with SSH. Here is what SSH said:' + print child.before, child.after + print str(child) + sys.exit (1) + if i == 1: # In this case SSH does not have the public key cached. + child.sendline ('yes') + child.expect ('(?i)password') + if i == 2: + # This may happen if a public key was setup to automatically login. + # But beware, the COMMAND_PROMPT at this point is very trivial and + # could be fooled by some output in the MOTD or login message. + pass + if i == 3: + child.sendline(password) + # Now we are either at the command prompt or + # the login process is asking for our terminal type. + i = child.expect ([COMMAND_PROMPT, TERMINAL_PROMPT]) + if i == 1: + child.sendline (TERMINAL_TYPE) + child.expect (COMMAND_PROMPT) + # + # Set command prompt to something more unique. + # + COMMAND_PROMPT = "\[PEXPECT\]\$ " + child.sendline ("PS1='[PEXPECT]\$ '") # In case of sh-style + i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10) + if i == 0: + print "# Couldn't set sh-style prompt -- trying csh-style." + child.sendline ("set prompt='[PEXPECT]\$ '") + i = child.expect ([pexpect.TIMEOUT, COMMAND_PROMPT], timeout=10) + if i == 0: + print "Failed to set command prompt using sh or csh style." + print "Response was:" + print child.before + sys.exit (1) + + # Now we should be at the command prompt and ready to run some commands. + print '---------------------------------------' + print 'Report of commands run on remote host.' + print '---------------------------------------' + + # Run uname. + child.sendline ('uname -a') + child.expect (COMMAND_PROMPT) + print child.before + if 'linux' in child.before.lower(): + LINUX_MODE = 1 + else: + LINUX_MODE = 0 + + # Run and parse 'uptime'. + child.sendline ('uptime') + child.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])') + duration, users, av1, av5, av15 = child.match.groups() + days = '0' + hours = '0' + mins = '0' + if 'day' in duration: + child.match = re.search('([0-9]+)\s+day',duration) + days = str(int(child.match.group(1))) + if ':' in duration: + child.match = re.search('([0-9]+):([0-9]+)',duration) + hours = str(int(child.match.group(1))) + mins = str(int(child.match.group(2))) + if 'min' in duration: + child.match = re.search('([0-9]+)\s+min',duration) + mins = str(int(child.match.group(1))) + print + print 'Uptime: %s days, %s users, %s (1 min), %s (5 min), %s (15 min)' % ( + duration, users, av1, av5, av15) + child.expect (COMMAND_PROMPT) + + # Run iostat. + child.sendline ('iostat') + child.expect (COMMAND_PROMPT) + print child.before + + # Run vmstat. + child.sendline ('vmstat') + child.expect (COMMAND_PROMPT) + print child.before + + # Run free. + if LINUX_MODE: + child.sendline ('free') # Linux systems only. + child.expect (COMMAND_PROMPT) + print child.before + + # Run df. + child.sendline ('df') + child.expect (COMMAND_PROMPT) + print child.before + + # Run lsof. + child.sendline ('lsof') + child.expect (COMMAND_PROMPT) + print child.before + +# # Run netstat +# child.sendline ('netstat') +# child.expect (COMMAND_PROMPT) +# print child.before + +# # Run MySQL show status. +# child.sendline ('mysql -p -e "SHOW STATUS;"') +# child.expect (PASSWORD_PROMPT_MYSQL) +# child.sendline (password_mysql) +# child.expect (COMMAND_PROMPT) +# print +# print child.before + + # Now exit the remote host. + child.sendline ('exit') + index = child.expect([pexpect.EOF, "(?i)there are stopped jobs"]) + if index==1: + child.sendline("exit") + child.expect(EOF) + +if __name__ == "__main__": + + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/third_party/Python/module/pexpect-2.4/examples/passmass.py b/third_party/Python/module/pexpect-2.4/examples/passmass.py new file mode 100644 index 000000000000..b1e17b9cb036 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/passmass.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python + +"""Change passwords on the named machines. passmass host1 host2 host3 . . . +Note that login shell prompt on remote machine must end in # or $. """ + +import pexpect +import sys, getpass + +USAGE = '''passmass host1 host2 host3 . . .''' +COMMAND_PROMPT = '[$#] ' +TERMINAL_PROMPT = r'Terminal type\?' +TERMINAL_TYPE = 'vt100' +SSH_NEWKEY = r'Are you sure you want to continue connecting \(yes/no\)\?' + +def login(host, user, password): + + child = pexpect.spawn('ssh -l %s %s'%(user, host)) + fout = file ("LOG.TXT","wb") + child.setlog (fout) + + i = child.expect([pexpect.TIMEOUT, SSH_NEWKEY, '[Pp]assword: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + sys.exit (1) + if i == 1: # SSH does not have the public key. Just accept it. + child.sendline ('yes') + child.expect ('[Pp]assword: ') + child.sendline(password) + # Now we are either at the command prompt or + # the login process is asking for our terminal type. + i = child.expect (['Permission denied', TERMINAL_PROMPT, COMMAND_PROMPT]) + if i == 0: + print 'Permission denied on host:', host + sys.exit (1) + if i == 1: + child.sendline (TERMINAL_TYPE) + child.expect (COMMAND_PROMPT) + return child + +# (current) UNIX password: +def change_password(child, user, oldpassword, newpassword): + + child.sendline('passwd') + i = child.expect(['[Oo]ld [Pp]assword', '.current.*password', '[Nn]ew [Pp]assword']) + # Root does not require old password, so it gets to bypass the next step. + if i == 0 or i == 1: + child.sendline(oldpassword) + child.expect('[Nn]ew [Pp]assword') + child.sendline(newpassword) + i = child.expect(['[Nn]ew [Pp]assword', '[Rr]etype', '[Rr]e-enter']) + if i == 0: + print 'Host did not like new password. Here is what it said...' + print child.before + child.send (chr(3)) # Ctrl-C + child.sendline('') # This should tell remote passwd command to quit. + return + child.sendline(newpassword) + +def main(): + + if len(sys.argv) <= 1: + print USAGE + return 1 + + user = raw_input('Username: ') + password = getpass.getpass('Current Password: ') + newpassword = getpass.getpass('New Password: ') + newpasswordconfirm = getpass.getpass('Confirm New Password: ') + if newpassword != newpasswordconfirm: + print 'New Passwords do not match.' + return 1 + + for host in sys.argv[1:]: + child = login(host, user, password) + if child == None: + print 'Could not login to host:', host + continue + print 'Changing password on host:', host + change_password(child, user, password, newpassword) + child.expect(COMMAND_PROMPT) + child.sendline('exit') + +if __name__ == '__main__': + try: + main() + except pexpect.ExceptionPexpect, e: + print str(e) + diff --git a/third_party/Python/module/pexpect-2.4/examples/python.py b/third_party/Python/module/pexpect-2.4/examples/python.py new file mode 100644 index 000000000000..d8c986652e6d --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/python.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +"""This starts the python interpreter; captures the startup message; then gives +the user interactive control over the session. Why? For fun... """ + +# Don't do this unless you like being John Malkovich +# c = pexpect.spawn ('/usr/bin/env python ./python.py') + +import pexpect +c = pexpect.spawn ('/usr/bin/env python') +c.expect ('>>>') +print 'And now for something completely different...' +f = lambda s:s and f(s[1:])+s[0] # Makes a function to reverse a string. +print f(c.before) +print 'Yes, it\'s python, but it\'s backwards.' +print +print 'Escape character is \'^]\'.' +print c.after, +c.interact() +c.kill(1) +print 'is alive:', c.isalive() + diff --git a/third_party/Python/module/pexpect-2.4/examples/rippy.py b/third_party/Python/module/pexpect-2.4/examples/rippy.py new file mode 100644 index 000000000000..e89c314c323c --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/rippy.py @@ -0,0 +1,993 @@ +#!/usr/bin/env python + +"""Rippy! + +This script helps to convert video from one format to another. +This is useful for ripping DVD to mpeg4 video (XviD, DivX). + +Features: + * automatic crop detection + * mp3 audio compression with resampling options + * automatic bitrate calculation based on desired target size + * optional interlace removal, b/w video optimization, video scaling + +Run the script with no arguments to start with interactive prompts: + rippy.py +Run the script with the filename of a config to start automatic mode: + rippy.py rippy.conf + +After Rippy is finished it saves the current configuation in a file called +'rippy.conf' in the local directoy. This can be used to rerun process using the +exact same settings by passing the filename of the conf file as an argument to +Rippy. Rippy will read the options from the file instead of asking you for +options interactively. So if you run rippy with 'dry_run=1' then you can run +the process again later using the 'rippy.conf' file. Don't forget to edit +'rippy.conf' to set 'dry_run=0'! + +If you run rippy with 'dry_run' and 'verbose' true then the output generated is +valid command line commands. you could (in theory) cut-and-paste the commands +to a shell prompt. You will need to tweak some values such as crop area and bit +rate because these cannot be calculated in a dry run. This is useful if you +want to get an idea of what Rippy plans to do. + +For all the trouble that Rippy goes through to calculate the best bitrate for a +desired target video size it sometimes fails to get it right. Sometimes the +final video size will differ more than you wanted from the desired size, but if +you are really motivated and have a lot of time on your hands then you can run +Rippy again with a manually calculated bitrate. After all compression is done +the first time Rippy will recalculate the bitrate to give you the nearly exact +bitrate that would have worked. You can then edit the 'rippy.conf' file; set +the video_bitrate with this revised bitrate; and then run Rippy all over again. +There is nothing like 4-pass video compression to get it right! Actually, this +could be done in three passes since I don't need to do the second pass +compression before I calculate the revised bitrate. I'm also considering an +enhancement where Rippy would compress ten spread out chunks, 1-minute in +length to estimate the bitrate. + +Free, open source, and all that good stuff. +Rippy Copyright (c) 2006 Noah Spurrier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. + +Noah Spurrier +$Id: rippy.py 517 2008-08-18 22:23:56Z noah $ +""" + +import sys, os, re, math, stat, getopt, traceback, types, time +import pexpect + +__version__ = '1.2' +__revision__ = '$Revision: 11 $' +__all__ = ['main', __version__, __revision__] + +GLOBAL_LOGFILE_NAME = "rippy_%d.log" % os.getpid() +GLOBAL_LOGFILE = open (GLOBAL_LOGFILE_NAME, "wb") + +############################################################################### +# This giant section defines the prompts and defaults used in interactive mode. +############################################################################### +# Python dictionaries are unordered, so +# I have this list that maintains the order of the keys. +prompts_key_order = ( +'verbose_flag', +'dry_run_flag', +'video_source_filename', +'video_chapter', +'video_final_filename', +'video_length', +'video_aspect_ratio', +'video_scale', +'video_encode_passes', +'video_codec', +'video_fourcc_override', +'video_bitrate', +'video_bitrate_overhead', +'video_target_size', +'video_deinterlace_flag', +'video_crop_area', +'video_gray_flag', +'subtitle_id', +'audio_id', +'audio_codec', +'audio_raw_filename', +'audio_volume_boost', +'audio_sample_rate', +'audio_bitrate', +#'audio_lowpass_filter', +'delete_tmp_files_flag' +) +# +# The 'prompts' dictionary holds all the messages shown to the user in +# interactive mode. The 'prompts' dictionary schema is defined as follows: +# prompt_key : ( default value, prompt string, help string, level of difficulty (0,1,2) ) +# +prompts = { +'video_source_filename':("dvd://1", 'video source filename?', """This is the filename of the video that you want to convert from. +It can be any file that mencoder supports. +You can also choose a DVD device using the dvd://1 syntax. +Title 1 is usually the main title on a DVD.""",0), +'video_chapter':("none",'video chapter?',"""This is the chapter number. Usually disks such as TV series seasons will be divided into chapters. Maybe be set to none.""",0), +'video_final_filename':("video_final.avi", "video final filename?", """This is the name of the final video.""",0), +'audio_raw_filename':("audiodump.wav", "audio raw filename?", """This is the audio raw PCM filename. This is prior to compression. +Note that mplayer automatically names this audiodump.wav, so don't change this.""",1000), +#'audio_compressed_filename':("audiodump.mp3","Audio compressed filename?", """This is the name of the compressed audio that will be mixed +#into the final video. Normally you don't need to change this.""",2), +'video_length':("none","video length in seconds?","""This sets the length of the video in seconds. This is used to estimate the +bitrate for a target video file size. Set to 'calc' to have Rippy calculate +the length. Set to 'none' if you don't want rippy to estimate the bitrate -- +you will have to manually specify bitrate.""",1), +'video_aspect_ratio':("calc","aspect ratio?","""This sets the aspect ratio of the video. Most DVDs are 16/9 or 4/3.""",1), +'video_scale':("none","video scale?","""This scales the video to the given output size. The default is to do no scaling. +You may type in a resolution such as 320x240 or you may use presets. + qntsc: 352x240 (NTSC quarter screen) + qpal: 352x288 (PAL quarter screen) + ntsc: 720x480 (standard NTSC) + pal: 720x576 (standard PAL) + sntsc: 640x480 (square pixel NTSC) + spal: 768x576 (square pixel PAL)""",1), +'video_codec':("mpeg4","video codec?","""This is the video compression to use. This is passed directly to mencoder, so +any format that it recognizes should work. For XviD or DivX use mpeg4. +Almost all MS Windows systems support wmv2 out of the box. +Some common codecs include: +mjpeg, h263, h263p, h264, mpeg4, msmpeg4, wmv1, wmv2, mpeg1video, mpeg2video, huffyuv, ffv1. +""",2), +'audio_codec':("mp3","audio codec?","""This is the audio compression to use. This is passed directly to mencoder, so +any format that it recognizes will work. +Some common codecs include: +mp3, mp2, aac, pcm +See mencoder manual for details.""",2), +'video_fourcc_override':("XVID","force fourcc code?","""This forces the fourcc codec to the given value. XVID is safest for Windows. +The following are common fourcc values: + FMP4 - This is the mencoder default. This is the "real" value. + XVID - used by Xvid (safest) + DX50 - + MP4S - Microsoft""",2), +'video_encode_passes':("1","number of encode passes?","""This sets how many passes to use to encode the video. You can choose 1 or 2. +Using two pases takes twice as long as one pass, but produces a better +quality video. I found that the improvement is not that impressive.""",1), +'verbose_flag':("Y","verbose output?","""This sets verbose output. If true then all commands and arguments are printed +before they are run. This is useful to see exactly how commands are run.""",1), +'dry_run_flag':("N","dry run?","""This sets 'dry run' mode. If true then commands are not run. This is useful +if you want to see what would the script would do.""",1), +'video_bitrate':("calc","video bitrate?","""This sets the video bitrate. This overrides video_target_size. +Set to 'calc' to automatically estimate the bitrate based on the +video final target size. If you set video_length to 'none' then +you will have to specify this video_bitrate.""",1), +'video_target_size':("737280000","video final target size?","""This sets the target video size that you want to end up with. +This is over-ridden by video_bitrate. In other words, if you specify +video_bitrate then video_target_size is ignored. +Due to the unpredictable nature of VBR compression the final video size +may not exactly match. The following are common CDR sizes: + 180MB CDR (21 minutes) holds 193536000 bytes + 550MB CDR (63 minutes) holds 580608000 bytes + 650MB CDR (74 minutes) holds 681984000 bytes + 700MB CDR (80 minutes) holds 737280000 bytes""",0), +'video_bitrate_overhead':("1.0","bitrate overhead factor?","""Adjust this value if you want to leave more room for +other files such as subtitle files. +If you specify video_bitrate then this value is ignored.""",2), +'video_crop_area':("detect","crop area?","""This sets the crop area to remove black bars from the top or sides of the video. +This helps save space. Set to 'detect' to automatically detect the crop area. +Set to 'none' to not crop the video. Normally you don't need to change this.""",1), +'video_deinterlace_flag':("N","is the video interlaced?","""This sets the deinterlace flag. If set then mencoder will be instructed +to filter out interlace artifacts (using '-vf pp=md').""",1), +'video_gray_flag':("N","is the video black and white (gray)?","""This improves output for black and white video.""",1), +'subtitle_id':("None","Subtitle ID stream?","""This selects the subtitle stream to extract from the source video. +Normally, 0 is the English subtitle stream for a DVD. +Subtitles IDs with higher numbers may be other languages.""",1), +'audio_id':("128","audio ID stream?","""This selects the audio stream to extract from the source video. +If your source is a VOB file (DVD) then stream IDs start at 128. +Normally, 128 is the main audio track for a DVD. +Tracks with higher numbers may be other language dubs or audio commentary.""",1), +'audio_sample_rate':("32000","audio sample rate (Hz) 48000, 44100, 32000, 24000, 12000","""This sets the rate at which the compressed audio will be resampled. +DVD audio is 48 kHz whereas music CDs use 44.1 kHz. The higher the sample rate +the more space the audio track will take. That will leave less space for video. +32 kHz is a good trade-off if you are trying to fit a video onto a CD.""",1), +'audio_bitrate':("96","audio bitrate (kbit/s) 192, 128, 96, 64?","""This sets the bitrate for MP3 audio compression. +The higher the bitrate the more space the audio track will take. +That will leave less space for video. Most people find music to be acceptable +at 128 kBitS. 96 kBitS is a good trade-off if you are trying to fit a video onto a CD.""",1), +'audio_volume_boost':("none","volume dB boost?","""Many DVDs have very low audio volume. This sets an audio volume boost in Decibels. +Values of 6 to 10 usually adjust quiet DVDs to a comfortable level.""",1), +#'audio_lowpass_filter':("16","audio lowpass filter (kHz)?","""This sets the low-pass filter for the audio. +#Normally this should be half of the audio sample rate. +#This improves audio compression and quality. +#Normally you don't need to change this.""",1), +'delete_tmp_files_flag':("N","delete temporary files when finished?","""If Y then %s, audio_raw_filename, and 'divx2pass.log' will be deleted at the end."""%GLOBAL_LOGFILE_NAME,1) +} + +############################################################################## +# This is the important convert control function +############################################################################## +def convert (options): + """This is the heart of it all -- this performs an end-to-end conversion of + a video from one format to another. It requires a dictionary of options. + The conversion process will also add some keys to the dictionary + such as length of the video and crop area. The dictionary is returned. + This options dictionary could be used again to repeat the convert process + (it is also saved to rippy.conf as text). + """ + if options['subtitle_id'] is not None: + print "# extract subtitles" + apply_smart (extract_subtitles, options) + else: + print "# do not extract subtitles." + + # Optimization + # I really only need to calculate the exact video length if the user + # selected 'calc' for video_bitrate + # or + # selected 'detect' for video_crop_area. + if options['video_bitrate']=='calc' or options['video_crop_area']=='detect': + # As strange as it seems, the only reliable way to calculate the length + # of a video (in seconds) is to extract the raw, uncompressed PCM audio stream + # and then calculate the length of that. This is because MP4 video is VBR, so + # you cannot get exact time based on compressed size. + if options['video_length']=='calc': + print "# extract PCM raw audio to %s" % (options['audio_raw_filename']) + apply_smart (extract_audio, options) + options['video_length'] = apply_smart (get_length, options) + print "# Length of raw audio file : %d seconds (%0.2f minutes)" % (options['video_length'], float(options['video_length'])/60.0) + if options['video_bitrate']=='calc': + options['video_bitrate'] = options['video_bitrate_overhead'] * apply_smart (calc_video_bitrate, options) + print "# video bitrate : " + str(options['video_bitrate']) + if options['video_crop_area']=='detect': + options['video_crop_area'] = apply_smart (crop_detect, options) + print "# crop area : " + str(options['video_crop_area']) + print "# compression estimate" + print apply_smart (compression_estimate, options) + + print "# compress video" + apply_smart (compress_video, options) + 'audio_volume_boost', + + print "# delete temporary files:", + if options['delete_tmp_files_flag']: + print "yes" + apply_smart (delete_tmp_files, options) + else: + print "no" + + # Finish by saving options to rippy.conf and + # calclating if final_size is less than target_size. + o = ["# options used to create video\n"] + video_actual_size = get_filesize (options['video_final_filename']) + if options['video_target_size'] != 'none': + revised_bitrate = calculate_revised_bitrate (options['video_bitrate'], options['video_target_size'], video_actual_size) + o.append("# revised video_bitrate : %d\n" % revised_bitrate) + for k,v in options.iteritems(): + o.append (" %30s : %s\n" % (k, v)) + print '# '.join(o) + fout = open("rippy.conf","wb").write(''.join(o)) + print "# final actual video size = %d" % video_actual_size + if options['video_target_size'] != 'none': + if video_actual_size > options['video_target_size']: + print "# FINAL VIDEO SIZE IS GREATER THAN DESIRED TARGET" + print "# final video size is %d bytes over target size" % (video_actual_size - options['video_target_size']) + else: + print "# final video size is %d bytes under target size" % (options['video_target_size'] - video_actual_size) + print "# If you want to run the entire compression process all over again" + print "# to get closer to the target video size then trying using a revised" + print "# video_bitrate of %d" % revised_bitrate + + return options + +############################################################################## + +def exit_with_usage(exit_code=1): + print globals()['__doc__'] + print 'version:', globals()['__version__'] + sys.stdout.flush() + os._exit(exit_code) + +def check_missing_requirements (): + """This list of missing requirements (mencoder, mplayer, lame, and mkvmerge). + Returns None if all requirements are in the execution path. + """ + missing = [] + if pexpect.which("mencoder") is None: + missing.append("mencoder") + if pexpect.which("mplayer") is None: + missing.append("mplayer") + cmd = "mencoder -oac help" + (command_output, exitstatus) = run(cmd) + ar = re.findall("(mp3lame)", command_output) + if len(ar)==0: + missing.append("Mencoder was not compiled with mp3lame support.") + + #if pexpect.which("lame") is None: + # missing.append("lame") + #if pexpect.which("mkvmerge") is None: + # missing.append("mkvmerge") + if len(missing)==0: + return None + return missing + +def input_option (message, default_value="", help=None, level=0, max_level=0): + """This is a fancy raw_input function. + If the user enters '?' then the contents of help is printed. + + The 'level' and 'max_level' are used to adjust which advanced options + are printed. 'max_level' is the level of options that the user wants + to see. 'level' is the level of difficulty for this particular option. + If this level is <= the max_level the user wants then the + message is printed and user input is allowed; otherwise, the + default value is returned automatically without user input. + """ + if default_value != '': + message = "%s [%s] " % (message, default_value) + if level > max_level: + return default_value + while 1: + user_input = raw_input (message) + if user_input=='?': + print help + elif user_input=='': + return default_value + else: + break + return user_input + +def progress_callback (d=None): + """This callback simply prints a dot to show activity. + This is used when running external commands with pexpect.run. + """ + sys.stdout.write (".") + sys.stdout.flush() + +def run(cmd): + global GLOBAL_LOGFILE + print >>GLOBAL_LOGFILE, cmd + (command_output, exitstatus) = pexpect.run(cmd, events={pexpect.TIMEOUT:progress_callback}, timeout=5, withexitstatus=True, logfile=GLOBAL_LOGFILE) + if exitstatus != 0: + print "RUN FAILED. RETURNED EXIT STATUS:", exitstatus + print >>GLOBAL_LOGFILE, "RUN FAILED. RETURNED EXIT STATUS:", exitstatus + return (command_output, exitstatus) + +def apply_smart (func, args): + """This is similar to func(**args), but this won't complain about + extra keys in 'args'. This ignores keys in 'args' that are + not required by 'func'. This passes None to arguments that are + not defined in 'args'. That's fine for arguments with a default valeue, but + that's a bug for required arguments. I should probably raise a TypeError. + The func parameter can be a function reference or a string. + If it is a string then it is converted to a function reference. + """ + if type(func) is type(''): + if func in globals(): + func = globals()[func] + else: + raise NameError("name '%s' is not defined" % func) + if hasattr(func,'im_func'): # Handle case when func is a class method. + func = func.im_func + argcount = func.func_code.co_argcount + required_args = dict([(k,args.get(k)) for k in func.func_code.co_varnames[:argcount]]) + return func(**required_args) + +def count_unique (items): + """This takes a list and returns a sorted list of tuples with a count of each unique item in the list. + Example 1: + count_unique(['a','b','c','a','c','c','a','c','c']) + returns: + [(5,'c'), (3,'a'), (1,'b')] + Example 2 -- get the most frequent item in a list: + count_unique(['a','b','c','a','c','c','a','c','c'])[0][1] + returns: + 'c' + """ + stats = {} + for i in items: + if i in stats: + stats[i] = stats[i] + 1 + else: + stats[i] = 1 + stats = [(v, k) for k, v in stats.items()] + stats.sort() + stats.reverse() + return stats + +def calculate_revised_bitrate (video_bitrate, video_target_size, video_actual_size): + """This calculates a revised video bitrate given the video_bitrate used, + the actual size that resulted, and the video_target_size. + This can be used if you want to compress the video all over again in an + attempt to get closer to the video_target_size. + """ + return int(math.floor(video_bitrate * (float(video_target_size) / float(video_actual_size)))) + +def get_aspect_ratio (video_source_filename): + """This returns the aspect ratio of the original video. + This is usualy 1.78:1(16/9) or 1.33:1(4/3). + This function is very lenient. It basically guesses 16/9 whenever + it cannot figure out the aspect ratio. + """ + cmd = "mplayer '%s' -vo png -ao null -frames 1" % video_source_filename + (command_output, exitstatus) = run(cmd) + ar = re.findall("Movie-Aspect is ([0-9]+\.?[0-9]*:[0-9]+\.?[0-9]*)", command_output) + if len(ar)==0: + return '16/9' + if ar[0] == '1.78:1': + return '16/9' + if ar[0] == '1.33:1': + return '4/3' + return '16/9' + #idh = re.findall("ID_VIDEO_HEIGHT=([0-9]+)", command_output) + #if len(idw)==0 or len(idh)==0: + # print 'WARNING!' + # print 'Could not get aspect ration. Assuming 1.78:1 (16/9).' + # return 1.78 + #return float(idw[0])/float(idh[0]) +#ID_VIDEO_WIDTH=720 +#ID_VIDEO_HEIGHT=480 +#Movie-Aspect is 1.78:1 - prescaling to correct movie aspect. + + +def get_aid_list (video_source_filename): + """This returns a list of audio ids in the source video file. + TODO: Also extract ID_AID_nnn_LANG to associate language. Not all DVDs include this. + """ + cmd = "mplayer '%s' -vo null -ao null -frames 0 -identify" % video_source_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_AUDIO_ID=([0-9]+)", command_output) + idl.sort() + return idl + +def get_sid_list (video_source_filename): + """This returns a list of subtitle ids in the source video file. + TODO: Also extract ID_SID_nnn_LANG to associate language. Not all DVDs include this. + """ + cmd = "mplayer '%s' -vo null -ao null -frames 0 -identify" % video_source_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_SUBTITLE_ID=([0-9]+)", command_output) + idl.sort() + return idl + +def extract_audio (video_source_filename, audio_id=128, verbose_flag=0, dry_run_flag=0): + """This extracts the given audio_id track as raw uncompressed PCM from the given source video. + Note that mplayer always saves this to audiodump.wav. + At this time there is no way to set the output audio name. + """ + #cmd = "mplayer %(video_source_filename)s -vc null -vo null -aid %(audio_id)s -ao pcm:fast -noframedrop" % locals() + cmd = "mplayer -quiet '%(video_source_filename)s' -vc dummy -vo null -aid %(audio_id)s -ao pcm:fast -noframedrop" % locals() + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def extract_subtitles (video_source_filename, subtitle_id=0, verbose_flag=0, dry_run_flag=0): + """This extracts the given subtitle_id track as VOBSUB format from the given source video. + """ + cmd = "mencoder -quiet '%(video_source_filename)s' -o /dev/null -nosound -ovc copy -vobsubout subtitles -vobsuboutindex 0 -sid %(subtitle_id)s" % locals() + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def get_length (audio_raw_filename): + """This attempts to get the length of the media file (length is time in seconds). + This should not be confused with size (in bytes) of the file data. + This is best used on a raw PCM AUDIO file because mplayer cannot get an accurate + time for many compressed video and audio formats -- notably MPEG4 and MP3. + Weird... + This returns -1 if it cannot get the length of the given file. + """ + cmd = "mplayer %s -vo null -ao null -frames 0 -identify" % audio_raw_filename + (command_output, exitstatus) = run(cmd) + idl = re.findall("ID_LENGTH=([0-9.]*)", command_output) + idl.sort() + if len(idl) != 1: + print "ERROR: cannot get length of raw audio file." + print "command_output of mplayer identify:" + print command_output + print "parsed command_output:" + print str(idl) + return -1 + return float(idl[0]) + +def get_filesize (filename): + """This returns the number of bytes a file takes on storage.""" + return os.stat(filename)[stat.ST_SIZE] + +def calc_video_bitrate (video_target_size, audio_bitrate, video_length, extra_space=0, dry_run_flag=0): + """This gives an estimate of the video bitrate necessary to + fit the final target size. This will take into account room to + fit the audio and extra space if given (for container overhead or whatnot). + video_target_size is in bytes, + audio_bitrate is bits per second (96, 128, 256, etc.) ASSUMING CBR, + video_length is in seconds, + extra_space is in bytes. + a 180MB CDR (21 minutes) holds 193536000 bytes. + a 550MB CDR (63 minutes) holds 580608000 bytes. + a 650MB CDR (74 minutes) holds 681984000 bytes. + a 700MB CDR (80 minutes) holds 737280000 bytes. + """ + if dry_run_flag: + return -1 + if extra_space is None: extra_space = 0 + #audio_size = os.stat(audio_compressed_filename)[stat.ST_SIZE] + audio_size = (audio_bitrate * video_length * 1000) / 8.0 + video_target_size = video_target_size - audio_size - extra_space + return (int)(calc_video_kbitrate (video_target_size, video_length)) + +def calc_video_kbitrate (target_size, length_secs): + """Given a target byte size free for video data, this returns the bitrate in kBit/S. + For mencoder vbitrate 1 kBit = 1000 Bits -- not 1024 bits. + target_size = bitrate * 1000 * length_secs / 8 + target_size = bitrate * 125 * length_secs + bitrate = target_size/(125*length_secs) + """ + return int(target_size / (125.0 * length_secs)) + +def crop_detect (video_source_filename, video_length, dry_run_flag=0): + """This attempts to figure out the best crop for the given video file. + Basically it runs crop detect for 10 seconds on five different places in the video. + It picks the crop area that was most often detected. + """ + skip = int(video_length/9) # offset to skip (-ss option in mencoder) + sample_length = 10 + cmd1 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, skip, sample_length) + cmd2 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 2*skip, sample_length) + cmd3 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 4*skip, sample_length) + cmd4 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 6*skip, sample_length) + cmd5 = "mencoder '%s' -quiet -ss %d -endpos %d -o /dev/null -nosound -ovc lavc -vf cropdetect" % (video_source_filename, 8*skip, sample_length) + if dry_run_flag: + return "0:0:0:0" + (command_output1, exitstatus1) = run(cmd1) + (command_output2, exitstatus2) = run(cmd2) + (command_output3, exitstatus3) = run(cmd3) + (command_output4, exitstatus4) = run(cmd4) + (command_output5, exitstatus5) = run(cmd5) + idl = re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output1) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output2) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output3) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output4) + idl = idl + re.findall("-vf crop=([0-9]+:[0-9]+:[0-9]+:[0-9]+)", command_output5) + items_count = count_unique(idl) + return items_count[0][1] + + +def build_compression_command (video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None, seek_skip=None, seek_length=None, video_chapter=None): +#Notes:For DVD, VCD, and SVCD use acodec=mp2 and vcodec=mpeg2video: +#mencoder movie.avi -o movie.VOB -ovc lavc -oac lavc -lavcopts acodec=mp2:abitrate=224:vcodec=mpeg2video:vbitrate=2000 + + # + # build video filter (-vf) argument + # + video_filter = '' + if video_crop_area and video_crop_area.lower()!='none': + video_filter = video_filter + 'crop=%s' % video_crop_area + if video_deinterlace_flag: + if video_filter != '': + video_filter = video_filter + ',' + video_filter = video_filter + 'pp=md' + if video_scale and video_scale.lower()!='none': + if video_filter != '': + video_filter = video_filter + ',' + video_filter = video_filter + 'scale=%s' % video_scale + # optional video rotation -- were you holding your camera sideways? + #if video_filter != '': + # video_filter = video_filter + ',' + #video_filter = video_filter + 'rotate=2' + if video_filter != '': + video_filter = '-vf ' + video_filter + + # + # build chapter argument + # + if video_chapter is not None: + chapter = '-chapter %d-%d' %(video_chapter,video_chapter) + else: + chapter = '' +# chapter = '-chapter 2-2' + + # + # build audio_filter argument + # + audio_filter = '' + if audio_sample_rate: + if audio_filter != '': + audio_filter = audio_filter + ',' + audio_filter = audio_filter + 'lavcresample=%s' % audio_sample_rate + if audio_volume_boost is not None: + if audio_filter != '': + audio_filter = audio_filter + ',' + audio_filter = audio_filter + 'volume=%0.1f:1'%audio_volume_boost + if audio_filter != '': + audio_filter = '-af ' + audio_filter + # + #if audio_sample_rate: + # audio_filter = ('-srate %d ' % audio_sample_rate) + audio_filter + + # + # build lavcopts argument + # + #lavcopts = '-lavcopts vcodec=%s:vbitrate=%d:mbd=2:aspect=%s:acodec=%s:abitrate=%d:vpass=1' % (video_codec,video_bitrate,audio_codec,audio_bitrate) + lavcopts = '-lavcopts vcodec=%(video_codec)s:vbitrate=%(video_bitrate)d:mbd=2:aspect=%(video_aspect_ratio)s:acodec=%(audio_codec)s:abitrate=%(audio_bitrate)d:vpass=1' % (locals()) + if video_gray_flag: + lavcopts = lavcopts + ':gray' + + seek_filter = '' + if seek_skip is not None: + seek_filter = '-ss %s' % (str(seek_skip)) + if seek_length is not None: + seek_filter = seek_filter + ' -endpos %s' % (str(seek_length)) + +# cmd = "mencoder -quiet -info comment='Arkivist' '%(video_source_filename)s' %(seek_filter)s %(chapter)s -aid %(audio_id)s -o '%(video_final_filename)s' -ffourcc %(video_fourcc_override)s -ovc lavc -oac lavc %(lavcopts)s %(video_filter)s %(audio_filter)s" % locals() + cmd = "mencoder -quiet -info comment='Arkivist' '%(video_source_filename)s' %(seek_filter)s %(chapter)s -aid %(audio_id)s -o '%(video_final_filename)s' -ffourcc %(video_fourcc_override)s -ovc lavc -oac mp3lame %(lavcopts)s %(video_filter)s %(audio_filter)s" % locals() + return cmd + +def compression_estimate (video_length, video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None): + """This attempts to figure out the best compression ratio for a given set of compression options. + """ + # TODO Need to account for AVI overhead. + skip = int(video_length/9) # offset to skip (-ss option in mencoder) + sample_length = 10 + cmd1 = build_compression_command (video_source_filename, "compression_test_1.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip, sample_length) + cmd2 = build_compression_command (video_source_filename, "compression_test_2.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*2, sample_length) + cmd3 = build_compression_command (video_source_filename, "compression_test_3.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*4, sample_length) + cmd4 = build_compression_command (video_source_filename, "compression_test_4.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*6, sample_length) + cmd5 = build_compression_command (video_source_filename, "compression_test_5.avi", video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, skip*8, sample_length) + run(cmd1) + run(cmd2) + run(cmd3) + run(cmd4) + run(cmd5) + size = get_filesize ("compression_test_1.avi")+get_filesize ("compression_test_2.avi")+get_filesize ("compression_test_3.avi")+get_filesize ("compression_test_4.avi")+get_filesize ("compression_test_5.avi") + return (size / 5.0) + +def compress_video (video_source_filename, video_final_filename, video_target_size, audio_id=128, video_bitrate=1000, video_codec='mpeg4', audio_codec='mp3', video_fourcc_override='FMP4', video_gray_flag=0, video_crop_area=None, video_aspect_ratio='16/9', video_scale=None, video_encode_passes=2, video_deinterlace_flag=0, audio_volume_boost=None, audio_sample_rate=None, audio_bitrate=None, seek_skip=None, seek_length=None, video_chapter=None, verbose_flag=0, dry_run_flag=0): + """This compresses the video and audio of the given source video filename to the transcoded filename. + This does a two-pass compression (I'm assuming mpeg4, I should probably make this smarter for other formats). + """ + # + # do the first pass video compression + # + #cmd = "mencoder -quiet '%(video_source_filename)s' -ss 65 -endpos 20 -aid %(audio_id)s -o '%(video_final_filename)s' -ffourcc %(video_fourcc_override)s -ovc lavc -oac lavc %(lavcopts)s %(video_filter)s %(audio_filter)s" % locals() + + cmd = build_compression_command (video_source_filename, video_final_filename, video_target_size, audio_id, video_bitrate, video_codec, audio_codec, video_fourcc_override, video_gray_flag, video_crop_area, video_aspect_ratio, video_scale, video_encode_passes, video_deinterlace_flag, audio_volume_boost, audio_sample_rate, audio_bitrate, seek_skip, seek_length, video_chapter) + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + + # If not doing two passes then return early. + if video_encode_passes!='2': + return + + if verbose_flag: + video_actual_size = get_filesize (video_final_filename) + if video_actual_size > video_target_size: + print "=======================================================" + print "WARNING!" + print "First pass compression resulted in" + print "actual file size greater than target size." + print "Second pass will be too big." + print "=======================================================" + + # + # do the second pass video compression + # + cmd = cmd.replace ('vpass=1', 'vpass=2') + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + return + +def compress_audio (audio_raw_filename, audio_compressed_filename, audio_lowpass_filter=None, audio_sample_rate=None, audio_bitrate=None, verbose_flag=0, dry_run_flag=0): + """This is depricated. + This compresses the raw audio file to the compressed audio filename. + """ + cmd = 'lame -h --athaa-sensitivity 1' # --cwlimit 11" + if audio_lowpass_filter: + cmd = cmd + ' --lowpass ' + audio_lowpass_filter + if audio_bitrate: + #cmd = cmd + ' --abr ' + audio_bitrate + cmd = cmd + ' --cbr -b ' + audio_bitrate + if audio_sample_rate: + cmd = cmd + ' --resample ' + audio_sample_rate + cmd = cmd + ' ' + audio_raw_filename + ' ' + audio_compressed_filename + if verbose_flag: print cmd + if not dry_run_flag: + (command_output, exitstatus) = run(cmd) + print + if exitstatus != 0: + raise Exception('ERROR: lame failed to compress raw audio file.') + +def mux (video_final_filename, video_transcoded_filename, audio_compressed_filename, video_container_format, verbose_flag=0, dry_run_flag=0): + """This is depricated. I used to use a three-pass encoding where I would mix the audio track separately, but + this never worked very well (loss of audio sync).""" + if video_container_format.lower() == 'mkv': # Matroska + mux_mkv (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag, dry_run_flag) + if video_container_format.lower() == 'avi': + mux_avi (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag, dry_run_flag) + +def mux_mkv (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag=0, dry_run_flag=0): + """This is depricated.""" + cmd = 'mkvmerge -o %s --noaudio %s %s' % (video_final_filename, video_transcoded_filename, audio_compressed_filename) + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +def mux_avi (video_final_filename, video_transcoded_filename, audio_compressed_filename, verbose_flag=0, dry_run_flag=0): + """This is depricated.""" + pass +# cmd = "mencoder -quiet -oac copy -ovc copy -o '%s' -audiofile %s '%s'" % (video_final_filename, audio_compressed_filename, video_transcoded_filename) +# if verbose_flag: print cmd +# if not dry_run_flag: +# run(cmd) +# print + +def delete_tmp_files (audio_raw_filename, verbose_flag=0, dry_run_flag=0): + global GLOBAL_LOGFILE_NAME + file_list = ' '.join([GLOBAL_LOGFILE_NAME, 'divx2pass.log', audio_raw_filename ]) + cmd = 'rm -f ' + file_list + if verbose_flag: print cmd + if not dry_run_flag: + run(cmd) + print + +############################################################################## +# This is the interactive Q&A that is used if a conf file was not given. +############################################################################## +def interactive_convert (): + + global prompts, prompts_key_order + + print globals()['__doc__'] + print + print "==============================================" + print " Enter '?' at any question to get extra help." + print "==============================================" + print + + # Ask for the level of options the user wants. + # A lot of code just to print a string! + level_sort = {0:'', 1:'', 2:''} + for k in prompts: + level = prompts[k][3] + if level < 0 or level > 2: + continue + level_sort[level] += " " + prompts[k][1] + "\n" + level_sort_string = "This sets the level for advanced options prompts. Set 0 for simple, 1 for advanced, or 2 for expert.\n" + level_sort_string += "[0] Basic options:\n" + str(level_sort[0]) + "\n" + level_sort_string += "[1] Advanced options:\n" + str(level_sort[1]) + "\n" + level_sort_string += "[2] Expert options:\n" + str(level_sort[2]) + c = input_option("Prompt level (0, 1, or 2)?", "1", level_sort_string) + max_prompt_level = int(c) + + options = {} + for k in prompts_key_order: + if k == 'video_aspect_ratio': + guess_aspect = get_aspect_ratio(options['video_source_filename']) + options[k] = input_option (prompts[k][1], guess_aspect, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'audio_id': + aid_list = get_aid_list (options['video_source_filename']) + default_id = '128' + if max_prompt_level>=prompts[k][3]: + if len(aid_list) > 1: + print "This video has more than one audio stream. The following stream audio IDs were found:" + for aid in aid_list: + print " " + aid + default_id = aid_list[0] + else: + print "WARNING!" + print "Rippy was unable to get the list of audio streams from this video." + print "If reading directly from a DVD then the DVD device might be busy." + print "Using a default setting of stream id 128 (main audio on most DVDs)." + default_id = '128' + options[k] = input_option (prompts[k][1], default_id, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'subtitle_id': + sid_list = get_sid_list (options['video_source_filename']) + default_id = 'None' + if max_prompt_level>=prompts[k][3]: + if len(sid_list) > 0: + print "This video has one or more subtitle streams. The following stream subtitle IDs were found:" + for sid in sid_list: + print " " + sid + #default_id = sid_list[0] + default_id = prompts[k][0] + else: + print "WARNING!" + print "Unable to get the list of subtitle streams from this video. It may have none." + print "Setting default to None." + default_id = 'None' + options[k] = input_option (prompts[k][1], default_id, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'audio_lowpass_filter': + lowpass_default = "%.1f" % (math.floor(float(options['audio_sample_rate']) / 2.0)) + options[k] = input_option (prompts[k][1], lowpass_default, prompts[k][2], prompts[k][3], max_prompt_level) + elif k == 'video_bitrate': + if options['video_length'].lower() == 'none': + options[k] = input_option (prompts[k][1], '1000', prompts[k][2], prompts[k][3], max_prompt_level) + else: + options[k] = input_option (prompts[k][1], prompts[k][0], prompts[k][2], prompts[k][3], max_prompt_level) + else: + # don't bother asking for video_target_size or video_bitrate_overhead if video_bitrate was set + if (k=='video_target_size' or k=='video_bitrate_overhead') and options['video_bitrate']!='calc': + continue + # don't bother with crop area if video length is none + if k == 'video_crop_area' and options['video_length'].lower() == 'none': + options['video_crop_area'] = 'none' + continue + options[k] = input_option (prompts[k][1], prompts[k][0], prompts[k][2], prompts[k][3], max_prompt_level) + + #options['video_final_filename'] = options['video_final_filename'] + "." + options['video_container_format'] + + print "==========================================================================" + print "Ready to Rippy!" + print + print "The following options will be used:" + for k,v in options.iteritems(): + print "%27s : %s" % (k, v) + + print + c = input_option("Continue?", "Y") + c = c.strip().lower() + if c[0] != 'y': + print "Exiting..." + os._exit(1) + return options + +def clean_options (d): + """This validates and cleans up the options dictionary. + After reading options interactively or from a conf file + we need to make sure that the values make sense and are + converted to the correct type. + 1. Any key with "_flag" in it becomes a boolean True or False. + 2. Values are normalized ("No", "None", "none" all become "none"; + "Calcluate", "c", "CALC" all become "calc"). + 3. Certain values are converted from string to int. + 4. Certain combinations of options are invalid or override each other. + This is a rather annoying function, but then so it most cleanup work. + """ + for k in d: + d[k] = d[k].strip() + # convert all flag options to 0 or 1 + if '_flag' in k: + if type(d[k]) is types.StringType: + if d[k].strip().lower()[0] in 'yt1': #Yes, True, 1 + d[k] = 1 + else: + d[k] = 0 + d['video_bitrate'] = d['video_bitrate'].lower() + if d['video_bitrate'][0]=='c': + d['video_bitrate']='calc' + else: + d['video_bitrate'] = int(float(d['video_bitrate'])) + try: + d['video_target_size'] = int(d['video_target_size']) + # shorthand magic numbers get automatically expanded + if d['video_target_size'] == 180: + d['video_target_size'] = 193536000 + elif d['video_target_size'] == 550: + d['video_target_size'] = 580608000 + elif d['video_target_size'] == 650: + d['video_target_size'] = 681984000 + elif d['video_target_size'] == 700: + d['video_target_size'] = 737280000 + except: + d['video_target_size'] = 'none' + + try: + d['video_chapter'] = int(d['video_chapter']) + except: + d['video_chapter'] = None + + try: + d['subtitle_id'] = int(d['subtitle_id']) + except: + d['subtitle_id'] = None + + try: + d['video_bitrate_overhead'] = float(d['video_bitrate_overhead']) + except: + d['video_bitrate_overhead'] = -1.0 + + d['audio_bitrate'] = int(d['audio_bitrate']) + d['audio_sample_rate'] = int(d['audio_sample_rate']) + d['audio_volume_boost'] = d['audio_volume_boost'].lower() + if d['audio_volume_boost'][0]=='n': + d['audio_volume_boost'] = None + else: + d['audio_volume_boost'] = d['audio_volume_boost'].replace('db','') + d['audio_volume_boost'] = float(d['audio_volume_boost']) + +# assert (d['video_bitrate']=='calc' and d['video_target_size']!='none') +# or (d['video_bitrate']!='calc' and d['video_target_size']=='none') + + d['video_scale'] = d['video_scale'].lower() + if d['video_scale'][0]=='n': + d['video_scale']='none' + else: + al = re.findall("([0-9]+).*?([0-9]+)", d['video_scale']) + d['video_scale']=al[0][0]+':'+al[0][1] + d['video_crop_area'] = d['video_crop_area'].lower() + if d['video_crop_area'][0]=='n': + d['video_crop_area']='none' + d['video_length'] = d['video_length'].lower() + if d['video_length'][0]=='c': + d['video_length']='calc' + elif d['video_length'][0]=='n': + d['video_length']='none' + else: + d['video_length'] = int(float(d['video_length'])) + if d['video_length']==0: + d['video_length'] = 'none' + assert (not (d['video_length']=='none' and d['video_bitrate']=='calc')) + return d + +def main (): + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + command_line_options = dict(optlist) + # There are a million ways to cry for help. These are but a few of them. + if [elem for elem in command_line_options if elem in ['-h','--h','-?','--?','--help']]: + exit_with_usage(0) + + missing = check_missing_requirements() + if missing is not None: + print + print "==========================================================================" + print "ERROR!" + print "Some required external commands are missing." + print "please install the following packages:" + print str(missing) + print "==========================================================================" + print + c = input_option("Continue?", "Y") + c = c.strip().lower() + if c[0] != 'y': + print "Exiting..." + os._exit(1) + + if len(args) > 0: + # cute one-line string-to-dictionary parser (two-lines if you count this comment): + options = dict(re.findall('([^: \t\n]*)\s*:\s*(".*"|[^ \t\n]*)', file(args[0]).read())) + options = clean_options(options) + convert (options) + else: + options = interactive_convert () + options = clean_options(options) + convert (options) + print "# Done!" + +if __name__ == "__main__": + try: + start_time = time.time() + print time.asctime() + main() + print time.asctime() + print "TOTAL TIME IN MINUTES:", + print (time.time() - start_time) / 60.0 + except Exception, e: + tb_dump = traceback.format_exc() + print "==========================================================================" + print "ERROR -- Unexpected exception in script." + print str(e) + print str(tb_dump) + print "==========================================================================" + print >>GLOBAL_LOGFILE, "==========================================================================" + print >>GLOBAL_LOGFILE, "ERROR -- Unexpected exception in script." + print >>GLOBAL_LOGFILE, str(e) + print >>GLOBAL_LOGFILE, str(tb_dump) + print >>GLOBAL_LOGFILE, "==========================================================================" + exit_with_usage(3) + diff --git a/third_party/Python/module/pexpect-2.4/examples/script.py b/third_party/Python/module/pexpect-2.4/examples/script.py new file mode 100644 index 000000000000..908b91241a63 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/script.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python + +"""This spawns a sub-shell (bash) and gives the user interactive control. The +entire shell session is logged to a file called script.log. This behaves much +like the classic BSD command 'script'. + +./script.py [-a] [-c command] {logfilename} + + logfilename : This is the name of the log file. Default is script.log. + -a : Append to log file. Default is to overwrite log file. + -c : spawn command. Default is to spawn the sh shell. + +Example: + + This will start a bash shell and append to the log named my_session.log: + + ./script.py -a -c bash my_session.log + +""" + +import os, sys, time, getopt +import signal, fcntl, termios, struct +import traceback +import pexpect + +global_pexpect_instance = None # Used by signal handler + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def main(): + + ###################################################################### + # Parse the options, arguments, get ready, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?ac:', ['help','h','?']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + if len(args) > 1: + exit_with_usage() + + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print "Help:" + exit_with_usage() + + if len(args) == 1: + script_filename = args[0] + else: + script_filename = "script.log" + if '-a' in options: + fout = file (script_filename, "ab") + else: + fout = file (script_filename, "wb") + if '-c' in options: + command = options['-c'] + else: + command = "sh" + + # Begin log with date/time in the form CCCCyymm.hhmmss + fout.write ('# %4d%02d%02d.%02d%02d%02d \n' % time.localtime()[:-3]) + + ###################################################################### + # Start the interactive session + ###################################################################### + p = pexpect.spawn(command) + p.logfile = fout + global global_pexpect_instance + global_pexpect_instance = p + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + + print "Script recording started. Type ^] (ASCII 29) to escape from the script shell." + p.interact(chr(29)) + fout.close() + return 0 + +def sigwinch_passthrough (sig, data): + + # Check for buggy platforms (see pexpect.setwinsize()). + if 'TIOCGWINSZ' in dir(termios): + TIOCGWINSZ = termios.TIOCGWINSZ + else: + TIOCGWINSZ = 1074295912 # assume + s = struct.pack ("HHHH", 0, 0, 0, 0) + a = struct.unpack ('HHHH', fcntl.ioctl(sys.stdout.fileno(), TIOCGWINSZ , s)) + global global_pexpect_instance + global_pexpect_instance.setwinsize(a[0],a[1]) + +if __name__ == "__main__": + try: + main() + except SystemExit, e: + raise e + except Exception, e: + print "ERROR" + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/third_party/Python/module/pexpect-2.4/examples/ssh_session.py b/third_party/Python/module/pexpect-2.4/examples/ssh_session.py new file mode 100644 index 000000000000..4d0e228a7e33 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/ssh_session.py @@ -0,0 +1,94 @@ +# +# Eric S. Raymond +# +# Greatly modified by Nigel W. Moriarty +# April 2003 +# +from pexpect import * +import os, sys +import getpass +import time + +class ssh_session: + + "Session with extra state including the password to be used." + + def __init__(self, user, host, password=None, verbose=0): + + self.user = user + self.host = host + self.verbose = verbose + self.password = password + self.keys = [ + 'authenticity', + 'assword:', + '@@@@@@@@@@@@', + 'Command not found.', + EOF, + ] + + self.f = open('ssh.out','w') + + def __repr__(self): + + outl = 'class :'+self.__class__.__name__ + for attr in self.__dict__: + if attr == 'password': + outl += '\n\t'+attr+' : '+'*'*len(self.password) + else: + outl += '\n\t'+attr+' : '+str(getattr(self, attr)) + return outl + + def __exec(self, command): + + "Execute a command on the remote host. Return the output." + child = spawn(command, + #timeout=10, + ) + if self.verbose: + sys.stderr.write("-> " + command + "\n") + seen = child.expect(self.keys) + self.f.write(str(child.before) + str(child.after)+'\n') + if seen == 0: + child.sendline('yes') + seen = child.expect(self.keys) + if seen == 1: + if not self.password: + self.password = getpass.getpass('Remote password: ') + child.sendline(self.password) + child.readline() + time.sleep(5) + # Added to allow the background running of remote process + if not child.isalive(): + seen = child.expect(self.keys) + if seen == 2: + lines = child.readlines() + self.f.write(lines) + if self.verbose: + sys.stderr.write("<- " + child.before + "|\n") + try: + self.f.write(str(child.before) + str(child.after)+'\n') + except: + pass + self.f.close() + return child.before + + def ssh(self, command): + + return self.__exec("ssh -l %s %s \"%s\"" \ + % (self.user,self.host,command)) + + def scp(self, src, dst): + + return self.__exec("scp %s %s@%s:%s" \ + % (src, session.user, session.host, dst)) + + def exists(self, file): + + "Retrieve file permissions of specified remote file." + seen = self.ssh("/bin/ls -ld %s" % file) + if string.find(seen, "No such file") > -1: + return None # File doesn't exist + else: + return seen.split()[0] # Return permission field of listing. + diff --git a/third_party/Python/module/pexpect-2.4/examples/ssh_tunnel.py b/third_party/Python/module/pexpect-2.4/examples/ssh_tunnel.py new file mode 100644 index 000000000000..3c8bc09514b4 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/ssh_tunnel.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python + +"""This starts an SSH tunnel to a given host. If the SSH process ever dies then +this script will detect that and restart it. I use this under Cygwin to keep +open encrypted tunnels to port 25 (SMTP), port 143 (IMAP4), and port 110 +(POP3). I set my mail client to talk to localhost and I keep this script +running in the background. + +Note that this is a rather stupid script at the moment because it just looks to +see if any ssh process is running. It should really make sure that our specific +ssh process is running. The problem is that ssh is missing a very useful +feature. It has no way to report the process id of the background daemon that +it creates with the -f command. This would be a really useful script if I could +figure a way around this problem. """ + +import pexpect +import getpass +import time + +# SMTP:25 IMAP4:143 POP3:110 +tunnel_command = 'ssh -C -N -f -L 25:127.0.0.1:25 -L 143:127.0.0.1:143 -L 110:127.0.0.1:110 %(user)@%(host)' +host = raw_input('Hostname: ') +user = raw_input('Username: ') +X = getpass.getpass('Password: ') + +def get_process_info (): + + # This seems to work on both Linux and BSD, but should otherwise be considered highly UNportable. + + ps = pexpect.run ('ps ax -O ppid') + pass +def start_tunnel (): + try: + ssh_tunnel = pexpect.spawn (tunnel_command % globals()) + ssh_tunnel.expect ('password:') + time.sleep (0.1) + ssh_tunnel.sendline (X) + time.sleep (60) # Cygwin is slow to update process status. + ssh_tunnel.expect (pexpect.EOF) + + except Exception, e: + print str(e) + +def main (): + + while True: + ps = pexpect.spawn ('ps') + time.sleep (1) + index = ps.expect (['/usr/bin/ssh', pexpect.EOF, pexpect.TIMEOUT]) + if index == 2: + print 'TIMEOUT in ps command...' + print str(ps) + time.sleep (13) + if index == 1: + print time.asctime(), + print 'restarting tunnel' + start_tunnel () + time.sleep (11) + print 'tunnel OK' + else: + # print 'tunnel OK' + time.sleep (7) + +if __name__ == '__main__': + main () + +# This was for older SSH versions that didn't have -f option +#tunnel_command = 'ssh -C -n -L 25:%(host)s:25 -L 110:%(host)s:110 %(user)s@%(host)s -f nothing.sh' +#nothing_script = """#!/bin/sh +#while true; do sleep 53; done +#""" + diff --git a/third_party/Python/module/pexpect-2.4/examples/sshls.py b/third_party/Python/module/pexpect-2.4/examples/sshls.py new file mode 100644 index 000000000000..ef1ab9c23cd5 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/sshls.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python + +"""This runs 'ls -l' on a remote host using SSH. At the prompts enter hostname, +user, and password. + +$Id: sshls.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect +import getpass, os + +def ssh_command (user, host, password, command): + + """This runs a command on the remote host. This could also be done with the +pxssh class, but this demonstrates what that class does at a simpler level. +This returns a pexpect.spawn object. This handles the case when you try to +connect to a new host and ssh asks you if you want to accept the public key +fingerprint and continue connecting. """ + + ssh_newkey = 'Are you sure you want to continue connecting' + child = pexpect.spawn('ssh -l %s %s %s'%(user, host, command)) + i = child.expect([pexpect.TIMEOUT, ssh_newkey, 'password: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + return None + if i == 1: # SSH does not have the public key. Just accept it. + child.sendline ('yes') + child.expect ('password: ') + i = child.expect([pexpect.TIMEOUT, 'password: ']) + if i == 0: # Timeout + print 'ERROR!' + print 'SSH could not login. Here is what SSH said:' + print child.before, child.after + return None + child.sendline(password) + return child + +def main (): + + host = raw_input('Hostname: ') + user = raw_input('User: ') + password = getpass.getpass('Password: ') + child = ssh_command (user, host, password, '/bin/ls -l') + child.expect(pexpect.EOF) + print child.before + +if __name__ == '__main__': + try: + main() + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/third_party/Python/module/pexpect-2.4/examples/table_test.html b/third_party/Python/module/pexpect-2.4/examples/table_test.html new file mode 100644 index 000000000000..5dba0ecf0c89 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/table_test.html @@ -0,0 +1,106 @@ +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"> +<html> +<head> +<title>TEST</title> +</head> +<style type="text/css"> +a {color: #9f9; text-decoration: none} +a:hover {color: #0f0} +hr {color: #0f0} +html,table,body,textarea,input,form +{ +font-family: "Courier New", Courier, mono; +font-size: 8pt; +color: #0c0; +background-color: #020; +margin:0; +padding:0; +border:0; +} +input { background-color: #010; } +textarea { +border-width:1; +border-style:solid; +border-color:#0c0; +padding:3; +margin:3; +} +</style> +<script> +var foo="" + +" 123456789012345678901234567890123456789012345 789012345678901234567890123456789"+ +"0 2345678901234567890123456789012345678901234 6 89012345678901234567890123456789"+ +"01 34567890123456789012345678901234567890123 567 9012345678901234567890123456789"+ +"012 456789012345678901234567890123456789012 45678 012345678901234567890123456789"+ +"0123 5678901234567890123456789012345678901 3456789 12345678901234567890123456789"+ +"01234 67890123456789012345678901234567890 234567890 2345678901234567890123456789"+ +"012345 789012345678901234567890123456789 12345678901 345678901234567890123456789"+ +"0123456 8901234567890123456789012345678 0123456789012 45678901234567890123456789"+ +"01234567 90123456789012345678901234567 901234567890123 5678901234567890123456789"+ +"012345678 012345678901234567890123456 89012345678901234 678901234567890123456789"+ +"0123456789 1234567890123456789012345 7890123456789012345 78901234567890123456789"+ +"01234567890 23456789012345678901234 678901234567890123456 8901234567890123456789"+ +"012345678901 345678901234567890123 56789012345678901234567 901234567890123456789"+ +"0123456789012 4567890123456789012 4567890123456789012345678 0123456789012345678 "+ +"01234567890123 56789012345678901 345678901234567890123456789 12345678901234567 9"+ +"012345678901234 678901234567890 23456789012 567 01234567890 234567890123456 89"+ +"0123456789012345 7890123456789 123457789012 567 012345678901 3456789012345 789"+ +"01234567890123456 89012345678 012345678901234567890123456789012 45678901234 6789"+ +"012345678901234567 901234567 90123456789 12345678901 34567890123 567890123 56789"+ +"0123456789012345678 0123456 8901234567890 3456789 2345678901234 6789012 456789"+ +"01234567890123456789 12345 7890123456789012 0123456789012345 78901 3456789"+ +"012345678901234567890 234 67890123456789012345678901234567890123456 890 23456789"+ +"0123456789012345678901 3 5678901234567890123456789012345678901234567 9 123456789"+ +"01234567890123456789012 456789012345678901234567890123456789012345678 0123456789"; +function start2() +{ + // get the reference for the body + //var mybody = document.getElementsByTagName("body")[0]; + var mybody = document.getElementById("replace_me"); + var myroot = document.getElementById("a_parent"); + mytable = document.createElement("table"); + mytablebody = document.createElement("tbody"); + mytable.setAttribute("border","0"); + mytable.setAttribute("cellspacing","0"); + mytable.setAttribute("cellpadding","0"); + for(var j = 0; j < 24; j++) + { + mycurrent_row = document.createElement("tr"); + for(var i = 0; i < 80; i++) + { + mycurrent_cell = document.createElement("td"); + offset = (j*80)+i; + currenttext = document.createTextNode(foo.substring(offset,offset+1)); + mycurrent_cell.appendChild(currenttext); + mycurrent_row.appendChild(mycurrent_cell); + } + mytablebody.appendChild(mycurrent_row); + } + mytable.appendChild(mytablebody); + myroot.replaceChild(mytable,mybody); + //mybody.appendChild(mytable); +} +</script> +<body onload="start2();"> +<table align="LEFT" border="0" cellspacing="0" cellpadding="0"> +<div id="a_parent"> +<span id="replace_me"> +<tr align="left" valign="left"> + <td>/</td> + <td>h</td> + <td>o</td> + <td>m</td> + <td>e</td> + <td>/</td> + <td>n</td> + <td>o</td> + <td>a</td> + <td>h</td> + <td>/</td> + <td> </td> +</tr> +</table> +</span> +</div> +</body> +</html>
\ No newline at end of file diff --git a/third_party/Python/module/pexpect-2.4/examples/topip.py b/third_party/Python/module/pexpect-2.4/examples/topip.py new file mode 100644 index 000000000000..5bd63e2ef224 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/topip.py @@ -0,0 +1,267 @@ +#!/usr/bin/env python + +""" This runs netstat on a local or remote server. It calculates some simple +statistical information on the number of external inet connections. It groups +by IP address. This can be used to detect if one IP address is taking up an +excessive number of connections. It can also send an email alert if a given IP +address exceeds a threshold between runs of the script. This script can be used +as a drop-in Munin plugin or it can be used stand-alone from cron. I used this +on a busy web server that would sometimes get hit with denial of service +attacks. This made it easy to see if a script was opening many multiple +connections. A typical browser would open fewer than 10 connections at once. A +script might open over 100 simultaneous connections. + +./topip.py [-s server_hostname] [-u username] [-p password] {-a from_addr,to_addr} {-n N} {-v} {--ipv6} + + -s : hostname of the remote server to login to. + -u : username to user for login. + -p : password to user for login. + -n : print stddev for the the number of the top 'N' ipaddresses. + -v : verbose - print stats and list of top ipaddresses. + -a : send alert if stddev goes over 20. + -l : to log message to /var/log/topip.log + --ipv6 : this parses netstat output that includes ipv6 format. + Note that this actually only works with ipv4 addresses, but for versions of + netstat that print in ipv6 format. + --stdev=N : Where N is an integer. This sets the trigger point for alerts and logs. + Default is to trigger if max value is above 5 standard deviations. + +Example: + + This will print stats for the top IP addresses connected to the given host: + + ./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v + + This will send an alert email if the maxip goes over the stddev trigger value and + the the current top ip is the same as the last top ip (/tmp/topip.last): + + ./topip.py -s www.example.com -u mylogin -p mypassword -n 10 -v -a alert@example.com,user@example.com + + This will print the connection stats for the localhost in Munin format: + + ./topip.py + +Noah Spurrier + +$Id: topip.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect, pxssh # See http://pexpect.sourceforge.net/ +import os, sys, time, re, getopt, pickle, getpass, smtplib +import traceback +from pprint import pprint + +TOPIP_LOG_FILE = '/var/log/topip.log' +TOPIP_LAST_RUN_STATS = '/var/run/topip.last' + +def exit_with_usage(): + + print globals()['__doc__'] + os._exit(1) + +def stats(r): + + """This returns a dict of the median, average, standard deviation, min and max of the given sequence. + + >>> from topip import stats + >>> print stats([5,6,8,9]) + {'med': 8, 'max': 9, 'avg': 7.0, 'stddev': 1.5811388300841898, 'min': 5} + >>> print stats([1000,1006,1008,1014]) + {'med': 1008, 'max': 1014, 'avg': 1007.0, 'stddev': 5.0, 'min': 1000} + >>> print stats([1,3,4,5,18,16,4,3,3,5,13]) + {'med': 4, 'max': 18, 'avg': 6.8181818181818183, 'stddev': 5.6216817577237475, 'min': 1} + >>> print stats([1,3,4,5,18,16,4,3,3,5,13,14,5,6,7,8,7,6,6,7,5,6,4,14,7]) + {'med': 6, 'max': 18, 'avg': 7.0800000000000001, 'stddev': 4.3259218670706474, 'min': 1} + """ + + total = sum(r) + avg = float(total)/float(len(r)) + sdsq = sum([(i-avg)**2 for i in r]) + s = list(r) + s.sort() + return dict(zip(['med', 'avg', 'stddev', 'min', 'max'] , (s[len(s)//2], avg, (sdsq/len(r))**.5, min(r), max(r)))) + +def send_alert (message, subject, addr_from, addr_to, smtp_server='localhost'): + + """This sends an email alert. + """ + + message = 'From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n' % (addr_from, addr_to, subject) + message + server = smtplib.SMTP(smtp_server) + server.sendmail(addr_from, addr_to, message) + server.quit() + +def main(): + + ###################################################################### + ## Parse the options, arguments, etc. + ###################################################################### + try: + optlist, args = getopt.getopt(sys.argv[1:], 'h?valqs:u:p:n:', ['help','h','?','ipv6','stddev=']) + except Exception, e: + print str(e) + exit_with_usage() + options = dict(optlist) + + munin_flag = False + if len(args) > 0: + if args[0] == 'config': + print 'graph_title Netstat Connections per IP' + print 'graph_vlabel Socket connections per IP' + print 'connections_max.label max' + print 'connections_max.info Maximum number of connections per IP' + print 'connections_avg.label avg' + print 'connections_avg.info Average number of connections per IP' + print 'connections_stddev.label stddev' + print 'connections_stddev.info Standard deviation' + return 0 + elif args[0] != '': + print args, len(args) + return 0 + exit_with_usage() + if [elem for elem in options if elem in ['-h','--h','-?','--?','--help']]: + print 'Help:' + exit_with_usage() + if '-s' in options: + hostname = options['-s'] + else: + # if host was not specified then assume localhost munin plugin. + munin_flag = True + hostname = 'localhost' + # If localhost then don't ask for username/password. + if hostname != 'localhost' and hostname != '127.0.0.1': + if '-u' in options: + username = options['-u'] + else: + username = raw_input('username: ') + if '-p' in options: + password = options['-p'] + else: + password = getpass.getpass('password: ') + else: + use_localhost = True + + if '-l' in options: + log_flag = True + else: + log_flag = False + if '-n' in options: + average_n = int(options['-n']) + else: + average_n = None + if '-v' in options: + verbose = True + else: + verbose = False + if '-a' in options: + alert_flag = True + (alert_addr_from, alert_addr_to) = tuple(options['-a'].split(',')) + else: + alert_flag = False + if '--ipv6' in options: + ipv6_flag = True + else: + ipv6_flag = False + if '--stddev' in options: + stddev_trigger = float(options['--stddev']) + else: + stddev_trigger = 5 + + if ipv6_flag: + netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+::ffff:(\S+):(\S+)\s+.*?\r' + else: + netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(?:::ffff:)*(\S+):(\S+)\s+.*?\r' + #netstat_pattern = '(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+):(\S+)\s+.*?\r' + + # run netstat (either locally or via SSH). + if use_localhost: + p = pexpect.spawn('netstat -n -t') + PROMPT = pexpect.TIMEOUT + else: + p = pxssh.pxssh() + p.login(hostname, username, password) + p.sendline('netstat -n -t') + PROMPT = p.PROMPT + + # loop through each matching netstat_pattern and put the ip address in the list. + ip_list = {} + try: + while 1: + i = p.expect([PROMPT, netstat_pattern]) + if i == 0: + break + k = p.match.groups()[4] + if k in ip_list: + ip_list[k] = ip_list[k] + 1 + else: + ip_list[k] = 1 + except: + pass + + # remove a few common, uninteresting addresses from the dictionary. + ip_list = dict([ (key,value) for key,value in ip_list.items() if '192.168.' not in key]) + ip_list = dict([ (key,value) for key,value in ip_list.items() if '127.0.0.1' not in key]) + + # sort dict by value (count) + #ip_list = sorted(ip_list.iteritems(),lambda x,y:cmp(x[1], y[1]),reverse=True) + ip_list = ip_list.items() + if len(ip_list) < 1: + if verbose: print 'Warning: no networks connections worth looking at.' + return 0 + ip_list.sort(lambda x,y:cmp(y[1],x[1])) + + # generate some stats for the ip addresses found. + if average_n <= 1: + average_n = None + s = stats(zip(*ip_list[0:average_n])[1]) # The * unary operator treats the list elements as arguments + s['maxip'] = ip_list[0] + + # print munin-style or verbose results for the stats. + if munin_flag: + print 'connections_max.value', s['max'] + print 'connections_avg.value', s['avg'] + print 'connections_stddev.value', s['stddev'] + return 0 + if verbose: + pprint (s) + print + pprint (ip_list[0:average_n]) + + # load the stats from the last run. + try: + last_stats = pickle.load(file(TOPIP_LAST_RUN_STATS)) + except: + last_stats = {'maxip':None} + + if s['maxip'][1] > (s['stddev'] * stddev_trigger) and s['maxip']==last_stats['maxip']: + if verbose: print 'The maxip has been above trigger for two consecutive samples.' + if alert_flag: + if verbose: print 'SENDING ALERT EMAIL' + send_alert(str(s), 'ALERT on %s' % hostname, alert_addr_from, alert_addr_to) + if log_flag: + if verbose: print 'LOGGING THIS EVENT' + fout = file(TOPIP_LOG_FILE,'a') + #dts = time.strftime('%Y:%m:%d:%H:%M:%S', time.localtime()) + dts = time.asctime() + fout.write ('%s - %d connections from %s\n' % (dts,s['maxip'][1],str(s['maxip'][0]))) + fout.close() + + # save state to TOPIP_LAST_RUN_STATS + try: + pickle.dump(s, file(TOPIP_LAST_RUN_STATS,'w')) + os.chmod (TOPIP_LAST_RUN_STATS, 0664) + except: + pass + # p.logout() + +if __name__ == '__main__': + try: + main() + sys.exit(0) + except SystemExit, e: + raise e + except Exception, e: + print str(e) + traceback.print_exc() + os._exit(1) + diff --git a/third_party/Python/module/pexpect-2.4/examples/uptime.py b/third_party/Python/module/pexpect-2.4/examples/uptime.py new file mode 100644 index 000000000000..f5018dfe0c18 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/examples/uptime.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python + +"""This displays uptime information using uptime. This is redundant, +but it demonstrates expecting for a regular expression that uses subgroups. + +$Id: uptime.py 489 2007-11-28 23:40:34Z noah $ +""" + +import pexpect +import re + +# There are many different styles of uptime results. I try to parse them all. Yeee! +# Examples from different machines: +# [x86] Linux 2.4 (Redhat 7.3) +# 2:06pm up 63 days, 18 min, 3 users, load average: 0.32, 0.08, 0.02 +# [x86] Linux 2.4.18-14 (Redhat 8.0) +# 3:07pm up 29 min, 1 user, load average: 2.44, 2.51, 1.57 +# [PPC - G4] MacOS X 10.1 SERVER Edition +# 2:11PM up 3 days, 13:50, 3 users, load averages: 0.01, 0.00, 0.00 +# [powerpc] Darwin v1-58.corefa.com 8.2.0 Darwin Kernel Version 8.2.0 +# 10:35 up 18:06, 4 users, load averages: 0.52 0.47 0.36 +# [Sparc - R220] Sun Solaris (8) +# 2:13pm up 22 min(s), 1 user, load average: 0.02, 0.01, 0.01 +# [x86] Linux 2.4.18-14 (Redhat 8) +# 11:36pm up 4 days, 17:58, 1 user, load average: 0.03, 0.01, 0.00 +# AIX jwdir 2 5 0001DBFA4C00 +# 09:43AM up 23:27, 1 user, load average: 0.49, 0.32, 0.23 +# OpenBSD box3 2.9 GENERIC#653 i386 +# 6:08PM up 4 days, 22:26, 1 user, load averages: 0.13, 0.09, 0.08 + +# This parses uptime output into the major groups using regex group matching. +p = pexpect.spawn ('uptime') +p.expect('up\s+(.*?),\s+([0-9]+) users?,\s+load averages?: ([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9]),?\s+([0-9]+\.[0-9][0-9])') +duration, users, av1, av5, av15 = p.match.groups() + +# The duration is a little harder to parse because of all the different +# styles of uptime. I'm sure there is a way to do this all at once with +# one single regex, but I bet it would be hard to read and maintain. +# If anyone wants to send me a version using a single regex I'd be happy to see it. +days = '0' +hours = '0' +mins = '0' +if 'day' in duration: + p.match = re.search('([0-9]+)\s+day',duration) + days = str(int(p.match.group(1))) +if ':' in duration: + p.match = re.search('([0-9]+):([0-9]+)',duration) + hours = str(int(p.match.group(1))) + mins = str(int(p.match.group(2))) +if 'min' in duration: + p.match = re.search('([0-9]+)\s+min',duration) + mins = str(int(p.match.group(1))) + +# Print the parsed fields in CSV format. +print 'days, hours, minutes, users, cpu avg 1 min, cpu avg 5 min, cpu avg 15 min' +print '%s, %s, %s, %s, %s, %s, %s' % (days, hours, mins, users, av1, av5, av15) + diff --git a/third_party/Python/module/pexpect-2.4/fdpexpect.py b/third_party/Python/module/pexpect-2.4/fdpexpect.py new file mode 100644 index 000000000000..0ece98e6b941 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/fdpexpect.py @@ -0,0 +1,82 @@ +"""This is like pexpect, but will work on any file descriptor that you pass it. +So you are reponsible for opening and close the file descriptor. + +$Id: fdpexpect.py 505 2007-12-26 21:33:50Z noah $ +""" + +from pexpect import * +import os + +__all__ = ['fdspawn'] + +class fdspawn (spawn): + + """This is like pexpect.spawn but allows you to supply your own open file + descriptor. For example, you could use it to read through a file looking + for patterns, or to control a modem or serial device. """ + + def __init__ (self, fd, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None): + + """This takes a file descriptor (an int) or an object that support the + fileno() method (returning an int). All Python file-like objects + support fileno(). """ + + ### TODO: Add better handling of trying to use fdspawn in place of spawn + ### TODO: (overload to allow fdspawn to also handle commands as spawn does. + + if type(fd) != type(0) and hasattr(fd, 'fileno'): + fd = fd.fileno() + + if type(fd) != type(0): + raise ExceptionPexpect ('The fd argument is not an int. If this is a command string then maybe you want to use pexpect.spawn.') + + try: # make sure fd is a valid file descriptor + os.fstat(fd) + except OSError: + raise ExceptionPexpect, 'The fd argument is not a valid file descriptor.' + + self.args = None + self.command = None + spawn.__init__(self, None, args, timeout, maxread, searchwindowsize, logfile) + self.child_fd = fd + self.own_fd = False + self.closed = False + self.name = '<file descriptor %d>' % fd + + def __del__ (self): + + return + + def close (self): + + if self.child_fd == -1: + return + if self.own_fd: + self.close (self) + else: + self.flush() + os.close(self.child_fd) + self.child_fd = -1 + self.closed = True + + def isalive (self): + + """This checks if the file descriptor is still valid. If os.fstat() + does not raise an exception then we assume it is alive. """ + + if self.child_fd == -1: + return False + try: + os.fstat(self.child_fd) + return True + except: + return False + + def terminate (self, force=False): + + raise ExceptionPexpect ('This method is not valid for file descriptors.') + + def kill (self, sig): + + return + diff --git a/third_party/Python/module/pexpect-2.4/pexpect.py b/third_party/Python/module/pexpect-2.4/pexpect.py new file mode 100644 index 000000000000..0bb0a84c1415 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/pexpect.py @@ -0,0 +1,1844 @@ +"""Pexpect is a Python module for spawning child applications and controlling +them automatically. Pexpect can be used for automating interactive applications +such as ssh, ftp, passwd, telnet, etc. It can be used to a automate setup +scripts for duplicating software package installations on different servers. It +can be used for automated software testing. Pexpect is in the spirit of Don +Libes' Expect, but Pexpect is pure Python. Other Expect-like modules for Python +require TCL and Expect or require C extensions to be compiled. Pexpect does not +use C, Expect, or TCL extensions. It should work on any platform that supports +the standard Python pty module. The Pexpect interface focuses on ease of use so +that simple tasks are easy. + +There are two main interfaces to Pexpect -- the function, run() and the class, +spawn. You can call the run() function to execute a command and return the +output. This is a handy replacement for os.system(). + +For example:: + + pexpect.run('ls -la') + +The more powerful interface is the spawn class. You can use this to spawn an +external child command and then interact with the child by sending lines and +expecting responses. + +For example:: + + child = pexpect.spawn('scp foo myname@host.example.com:.') + child.expect ('Password:') + child.sendline (mypassword) + +This works even for commands that ask for passwords or other input outside of +the normal stdio streams. + +Credits: Noah Spurrier, Richard Holden, Marco Molteni, Kimberley Burchett, +Robert Stone, Hartmut Goebel, Chad Schroeder, Erick Tryzelaar, Dave Kirby, Ids +vander Molen, George Todd, Noel Taylor, Nicolas D. Cesar, Alexander Gattin, +Jacques-Etienne Baudoux, Geoffrey Marshall, Francisco Lourenco, Glen Mabey, +Karthik Gurusamy, Fernando Perez, Corey Minyard, Jon Cohen, Guillaume +Chazarain, Andrew Ryan, Nick Craig-Wood, Andrew Stone, Jorgen Grahn, John +Spiegel, Jan Grant (Let me know if I forgot anyone.) + +Free, open source, and all that good stuff. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +Pexpect Copyright (c) 2008 Noah Spurrier +http://pexpect.sourceforge.net/ + +$Id: pexpect.py 516 2008-05-23 20:46:01Z noah $ +""" + +try: + import os, sys, time + import select + import string + import re + import struct + import resource + import types + import pty + import tty + import termios + import fcntl + import errno + import traceback + import signal +except ImportError, e: + raise ImportError (str(e) + """ + +A critical module was not found. Probably this operating system does not +support it. Pexpect is intended for UNIX-like operating systems.""") + +__version__ = '2.4' +__revision__ = '$Revision: 516 $' +__all__ = ['ExceptionPexpect', 'EOF', 'TIMEOUT', 'spawn', 'run', 'which', + 'split_command_line', '__version__', '__revision__'] + +# Exception classes used by this module. +class ExceptionPexpect(Exception): + + """Base class for all exceptions raised by this module. + """ + + def __init__(self, value): + + self.value = value + + def __str__(self): + + return str(self.value) + + def get_trace(self): + + """This returns an abbreviated stack trace with lines that only concern + the caller. In other words, the stack trace inside the Pexpect module + is not included. """ + + tblist = traceback.extract_tb(sys.exc_info()[2]) + #tblist = filter(self.__filter_not_pexpect, tblist) + tblist = [item for item in tblist if self.__filter_not_pexpect(item)] + tblist = traceback.format_list(tblist) + return ''.join(tblist) + + def __filter_not_pexpect(self, trace_list_item): + + """This returns True if list item 0 the string 'pexpect.py' in it. """ + + if trace_list_item[0].find('pexpect.py') == -1: + return True + else: + return False + +class EOF(ExceptionPexpect): + + """Raised when EOF is read from a child. This usually means the child has exited.""" + +class TIMEOUT(ExceptionPexpect): + + """Raised when a read time exceeds the timeout. """ + +##class TIMEOUT_PATTERN(TIMEOUT): +## """Raised when the pattern match time exceeds the timeout. +## This is different than a read TIMEOUT because the child process may +## give output, thus never give a TIMEOUT, but the output +## may never match a pattern. +## """ +##class MAXBUFFER(ExceptionPexpect): +## """Raised when a scan buffer fills before matching an expected pattern.""" + +def run (command, timeout=-1, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None): + + """ + This function runs the given command; waits for it to finish; then + returns all output as a string. STDERR is included in output. If the full + path to the command is not given then the path is searched. + + Note that lines are terminated by CR/LF (\\r\\n) combination even on + UNIX-like systems because this is the standard for pseudo ttys. If you set + 'withexitstatus' to true, then run will return a tuple of (command_output, + exitstatus). If 'withexitstatus' is false then this returns just + command_output. + + The run() function can often be used instead of creating a spawn instance. + For example, the following code uses spawn:: + + from pexpect import * + child = spawn('scp foo myname@host.example.com:.') + child.expect ('(?i)password') + child.sendline (mypassword) + + The previous code can be replace with the following:: + + from pexpect import * + run ('scp foo myname@host.example.com:.', events={'(?i)password': mypassword}) + + Examples + ======== + + Start the apache daemon on the local machine:: + + from pexpect import * + run ("/usr/local/apache/bin/apachectl start") + + Check in a file using SVN:: + + from pexpect import * + run ("svn ci -m 'automatic commit' my_file.py") + + Run a command and capture exit status:: + + from pexpect import * + (command_output, exitstatus) = run ('ls -l /bin', withexitstatus=1) + + Tricky Examples + =============== + + The following will run SSH and execute 'ls -l' on the remote machine. The + password 'secret' will be sent if the '(?i)password' pattern is ever seen:: + + run ("ssh username@machine.example.com 'ls -l'", events={'(?i)password':'secret\\n'}) + + This will start mencoder to rip a video from DVD. This will also display + progress ticks every 5 seconds as it runs. For example:: + + from pexpect import * + def print_ticks(d): + print d['event_count'], + run ("mencoder dvd://1 -o video.avi -oac copy -ovc copy", events={TIMEOUT:print_ticks}, timeout=5) + + The 'events' argument should be a dictionary of patterns and responses. + Whenever one of the patterns is seen in the command out run() will send the + associated response string. Note that you should put newlines in your + string if Enter is necessary. The responses may also contain callback + functions. Any callback is function that takes a dictionary as an argument. + The dictionary contains all the locals from the run() function, so you can + access the child spawn object or any other variable defined in run() + (event_count, child, and extra_args are the most useful). A callback may + return True to stop the current run process otherwise run() continues until + the next event. A callback may also return a string which will be sent to + the child. 'extra_args' is not used by directly run(). It provides a way to + pass data to a callback function through run() through the locals + dictionary passed to a callback. """ + + if timeout == -1: + child = spawn(command, maxread=2000, logfile=logfile, cwd=cwd, env=env) + else: + child = spawn(command, timeout=timeout, maxread=2000, logfile=logfile, cwd=cwd, env=env) + if events is not None: + patterns = events.keys() + responses = events.values() + else: + patterns=None # We assume that EOF or TIMEOUT will save us. + responses=None + child_result_list = [] + event_count = 0 + while 1: + try: + index = child.expect (patterns) + if type(child.after) in types.StringTypes: + child_result_list.append(child.before + child.after) + else: # child.after may have been a TIMEOUT or EOF, so don't cat those. + child_result_list.append(child.before) + if type(responses[index]) in types.StringTypes: + child.send(responses[index]) + elif type(responses[index]) is types.FunctionType: + callback_result = responses[index](locals()) + sys.stdout.flush() + if type(callback_result) in types.StringTypes: + child.send(callback_result) + elif callback_result: + break + else: + raise TypeError ('The callback must be a string or function type.') + event_count = event_count + 1 + except TIMEOUT, e: + child_result_list.append(child.before) + break + except EOF, e: + child_result_list.append(child.before) + break + child_result = ''.join(child_result_list) + if withexitstatus: + child.close() + return (child_result, child.exitstatus) + else: + return child_result + +class spawn (object): + + """This is the main class interface for Pexpect. Use this class to start + and control child applications. """ + + def __init__(self, command, args=[], timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): + + """This is the constructor. The command parameter may be a string that + includes a command and any arguments to the command. For example:: + + child = pexpect.spawn ('/usr/bin/ftp') + child = pexpect.spawn ('/usr/bin/ssh user@example.com') + child = pexpect.spawn ('ls -latr /tmp') + + You may also construct it with a list of arguments like so:: + + child = pexpect.spawn ('/usr/bin/ftp', []) + child = pexpect.spawn ('/usr/bin/ssh', ['user@example.com']) + child = pexpect.spawn ('ls', ['-latr', '/tmp']) + + After this the child application will be created and will be ready to + talk to. For normal use, see expect() and send() and sendline(). + + Remember that Pexpect does NOT interpret shell meta characters such as + redirect, pipe, or wild cards (>, |, or *). This is a common mistake. + If you want to run a command and pipe it through another command then + you must also start a shell. For example:: + + child = pexpect.spawn('/bin/bash -c "ls -l | grep LOG > log_list.txt"') + child.expect(pexpect.EOF) + + The second form of spawn (where you pass a list of arguments) is useful + in situations where you wish to spawn a command and pass it its own + argument list. This can make syntax more clear. For example, the + following is equivalent to the previous example:: + + shell_cmd = 'ls -l | grep LOG > log_list.txt' + child = pexpect.spawn('/bin/bash', ['-c', shell_cmd]) + child.expect(pexpect.EOF) + + The maxread attribute sets the read buffer size. This is maximum number + of bytes that Pexpect will try to read from a TTY at one time. Setting + the maxread size to 1 will turn off buffering. Setting the maxread + value higher may help performance in cases where large amounts of + output are read back from the child. This feature is useful in + conjunction with searchwindowsize. + + The searchwindowsize attribute sets the how far back in the incomming + seach buffer Pexpect will search for pattern matches. Every time + Pexpect reads some data from the child it will append the data to the + incomming buffer. The default is to search from the beginning of the + imcomming buffer each time new data is read from the child. But this is + very inefficient if you are running a command that generates a large + amount of data where you want to match The searchwindowsize does not + effect the size of the incomming data buffer. You will still have + access to the full buffer after expect() returns. + + The logfile member turns on or off logging. All input and output will + be copied to the given file object. Set logfile to None to stop + logging. This is the default. Set logfile to sys.stdout to echo + everything to standard output. The logfile is flushed after each write. + + Example log input and output to a file:: + + child = pexpect.spawn('some_command') + fout = file('mylog.txt','w') + child.logfile = fout + + Example log to stdout:: + + child = pexpect.spawn('some_command') + child.logfile = sys.stdout + + The logfile_read and logfile_send members can be used to separately log + the input from the child and output sent to the child. Sometimes you + don't want to see everything you write to the child. You only want to + log what the child sends back. For example:: + + child = pexpect.spawn('some_command') + child.logfile_read = sys.stdout + + To separately log output sent to the child use logfile_send:: + + self.logfile_send = fout + + The delaybeforesend helps overcome a weird behavior that many users + were experiencing. The typical problem was that a user would expect() a + "Password:" prompt and then immediately call sendline() to send the + password. The user would then see that their password was echoed back + to them. Passwords don't normally echo. The problem is caused by the + fact that most applications print out the "Password" prompt and then + turn off stdin echo, but if you send your password before the + application turned off echo, then you get your password echoed. + Normally this wouldn't be a problem when interacting with a human at a + real keyboard. If you introduce a slight delay just before writing then + this seems to clear up the problem. This was such a common problem for + many users that I decided that the default pexpect behavior should be + to sleep just before writing to the child application. 1/20th of a + second (50 ms) seems to be enough to clear up the problem. You can set + delaybeforesend to 0 to return to the old behavior. Most Linux machines + don't like this to be below 0.03. I don't know why. + + Note that spawn is clever about finding commands on your path. + It uses the same logic that "which" uses to find executables. + + If you wish to get the exit status of the child you must call the + close() method. The exit or signal status of the child will be stored + in self.exitstatus or self.signalstatus. If the child exited normally + then exitstatus will store the exit return code and signalstatus will + be None. If the child was terminated abnormally with a signal then + signalstatus will store the signal value and exitstatus will be None. + If you need more detail you can also read the self.status member which + stores the status returned by os.waitpid. You can interpret this using + os.WIFEXITED/os.WEXITSTATUS or os.WIFSIGNALED/os.TERMSIG. """ + + self.STDIN_FILENO = pty.STDIN_FILENO + self.STDOUT_FILENO = pty.STDOUT_FILENO + self.STDERR_FILENO = pty.STDERR_FILENO + self.stdin = sys.stdin + self.stdout = sys.stdout + self.stderr = sys.stderr + + self.searcher = None + self.ignorecase = False + self.before = None + self.after = None + self.match = None + self.match_index = None + self.terminated = True + self.exitstatus = None + self.signalstatus = None + self.status = None # status returned by os.waitpid + self.flag_eof = False + self.pid = None + self.child_fd = -1 # initially closed + self.timeout = timeout + self.delimiter = EOF + self.logfile = logfile + self.logfile_read = None # input from child (read_nonblocking) + self.logfile_send = None # output to send (send, sendline) + self.maxread = maxread # max bytes to read at one time into buffer + self.buffer = '' # This is the read buffer. See maxread. + self.searchwindowsize = searchwindowsize # Anything before searchwindowsize point is preserved, but not searched. + # Most Linux machines don't like delaybeforesend to be below 0.03 (30 ms). + self.delaybeforesend = 0.05 # Sets sleep time used just before sending data to child. Time in seconds. + self.delayafterclose = 0.1 # Sets delay in close() method to allow kernel time to update process status. Time in seconds. + self.delayafterterminate = 0.1 # Sets delay in terminate() method to allow kernel time to update process status. Time in seconds. + self.softspace = False # File-like object. + self.name = '<' + repr(self) + '>' # File-like object. + self.encoding = None # File-like object. + self.closed = True # File-like object. + self.cwd = cwd + self.env = env + self.__irix_hack = (sys.platform.lower().find('irix')>=0) # This flags if we are running on irix + # Solaris uses internal __fork_pty(). All others use pty.fork(). + if (sys.platform.lower().find('solaris')>=0) or (sys.platform.lower().find('sunos5')>=0): + self.use_native_pty_fork = False + else: + self.use_native_pty_fork = True + + + # allow dummy instances for subclasses that may not use command or args. + if command is None: + self.command = None + self.args = None + self.name = '<pexpect factory incomplete>' + else: + self._spawn (command, args) + + def __del__(self): + + """This makes sure that no system resources are left open. Python only + garbage collects Python objects. OS file descriptors are not Python + objects, so they must be handled explicitly. If the child file + descriptor was opened outside of this class (passed to the constructor) + then this does not close it. """ + + if not self.closed: + # It is possible for __del__ methods to execute during the + # teardown of the Python VM itself. Thus self.close() may + # trigger an exception because os.close may be None. + # -- Fernando Perez + try: + self.close() + except: + pass + + def __str__(self): + + """This returns a human-readable string that represents the state of + the object. """ + + s = [] + s.append(repr(self)) + s.append('version: ' + __version__ + ' (' + __revision__ + ')') + s.append('command: ' + str(self.command)) + s.append('args: ' + str(self.args)) + s.append('searcher: ' + str(self.searcher)) + s.append('buffer (last 100 chars): ' + str(self.buffer)[-100:]) + s.append('before (last 100 chars): ' + str(self.before)[-100:]) + s.append('after: ' + str(self.after)) + s.append('match: ' + str(self.match)) + s.append('match_index: ' + str(self.match_index)) + s.append('exitstatus: ' + str(self.exitstatus)) + s.append('flag_eof: ' + str(self.flag_eof)) + s.append('pid: ' + str(self.pid)) + s.append('child_fd: ' + str(self.child_fd)) + s.append('closed: ' + str(self.closed)) + s.append('timeout: ' + str(self.timeout)) + s.append('delimiter: ' + str(self.delimiter)) + s.append('logfile: ' + str(self.logfile)) + s.append('logfile_read: ' + str(self.logfile_read)) + s.append('logfile_send: ' + str(self.logfile_send)) + s.append('maxread: ' + str(self.maxread)) + s.append('ignorecase: ' + str(self.ignorecase)) + s.append('searchwindowsize: ' + str(self.searchwindowsize)) + s.append('delaybeforesend: ' + str(self.delaybeforesend)) + s.append('delayafterclose: ' + str(self.delayafterclose)) + s.append('delayafterterminate: ' + str(self.delayafterterminate)) + return '\n'.join(s) + + def _spawn(self,command,args=[]): + + """This starts the given command in a child process. This does all the + fork/exec type of stuff for a pty. This is called by __init__. If args + is empty then command will be parsed (split on spaces) and args will be + set to parsed arguments. """ + + # The pid and child_fd of this object get set by this method. + # Note that it is difficult for this method to fail. + # You cannot detect if the child process cannot start. + # So the only way you can tell if the child process started + # or not is to try to read from the file descriptor. If you get + # EOF immediately then it means that the child is already dead. + # That may not necessarily be bad because you may haved spawned a child + # that performs some task; creates no stdout output; and then dies. + + # If command is an int type then it may represent a file descriptor. + if type(command) == type(0): + raise ExceptionPexpect ('Command is an int type. If this is a file descriptor then maybe you want to use fdpexpect.fdspawn which takes an existing file descriptor instead of a command string.') + + if type (args) != type([]): + raise TypeError ('The argument, args, must be a list.') + + if args == []: + self.args = split_command_line(command) + self.command = self.args[0] + else: + self.args = args[:] # work with a copy + self.args.insert (0, command) + self.command = command + + command_with_path = which(self.command) + if command_with_path is None: + raise ExceptionPexpect ('The command was not found or was not executable: %s.' % self.command) + self.command = command_with_path + self.args[0] = self.command + + self.name = '<' + ' '.join (self.args) + '>' + + assert self.pid is None, 'The pid member should be None.' + assert self.command is not None, 'The command member should not be None.' + + if self.use_native_pty_fork: + try: + self.pid, self.child_fd = pty.fork() + except OSError, e: + raise ExceptionPexpect('Error! pty.fork() failed: ' + str(e)) + else: # Use internal __fork_pty + self.pid, self.child_fd = self.__fork_pty() + + if self.pid == 0: # Child + try: + self.child_fd = sys.stdout.fileno() # used by setwinsize() + self.setwinsize(24, 80) + except: + # Some platforms do not like setwinsize (Cygwin). + # This will cause problem when running applications that + # are very picky about window size. + # This is a serious limitation, but not a show stopper. + pass + # Do not allow child to inherit open file descriptors from parent. + max_fd = resource.getrlimit(resource.RLIMIT_NOFILE)[0] + for i in range (3, max_fd): + try: + os.close (i) + except OSError: + pass + + # I don't know why this works, but ignoring SIGHUP fixes a + # problem when trying to start a Java daemon with sudo + # (specifically, Tomcat). + signal.signal(signal.SIGHUP, signal.SIG_IGN) + + if self.cwd is not None: + os.chdir(self.cwd) + if self.env is None: + os.execv(self.command, self.args) + else: + os.execvpe(self.command, self.args, self.env) + + # Parent + self.terminated = False + self.closed = False + + def __fork_pty(self): + + """This implements a substitute for the forkpty system call. This + should be more portable than the pty.fork() function. Specifically, + this should work on Solaris. + + Modified 10.06.05 by Geoff Marshall: Implemented __fork_pty() method to + resolve the issue with Python's pty.fork() not supporting Solaris, + particularly ssh. Based on patch to posixmodule.c authored by Noah + Spurrier:: + + http://mail.python.org/pipermail/python-dev/2003-May/035281.html + + """ + + parent_fd, child_fd = os.openpty() + if parent_fd < 0 or child_fd < 0: + raise ExceptionPexpect, "Error! Could not open pty with os.openpty()." + + pid = os.fork() + if pid < 0: + raise ExceptionPexpect, "Error! Failed os.fork()." + elif pid == 0: + # Child. + os.close(parent_fd) + self.__pty_make_controlling_tty(child_fd) + + os.dup2(child_fd, 0) + os.dup2(child_fd, 1) + os.dup2(child_fd, 2) + + if child_fd > 2: + os.close(child_fd) + else: + # Parent. + os.close(child_fd) + + return pid, parent_fd + + def __pty_make_controlling_tty(self, tty_fd): + + """This makes the pseudo-terminal the controlling tty. This should be + more portable than the pty.fork() function. Specifically, this should + work on Solaris. """ + + child_name = os.ttyname(tty_fd) + + # Disconnect from controlling tty if still connected. + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); + if fd >= 0: + os.close(fd) + except: + # We are already disconnected. Perhaps we are running inside cron. + pass + + os.setsid() + + # Verify we are disconnected from controlling tty + try: + fd = os.open("/dev/tty", os.O_RDWR | os.O_NOCTTY); + if fd >= 0: + os.close(fd) + raise ExceptionPexpect, "Error! We are not disconnected from a controlling tty." + except: + # Good! We are disconnected from a controlling tty. + pass + + # Verify we can open child pty. + fd = os.open(child_name, os.O_RDWR); + if fd < 0: + raise ExceptionPexpect, "Error! Could not open child pty, " + child_name + else: + os.close(fd) + + # Verify we now have a controlling tty. + fd = os.open("/dev/tty", os.O_WRONLY) + if fd < 0: + raise ExceptionPexpect, "Error! Could not open controlling tty, /dev/tty" + else: + os.close(fd) + + def fileno (self): # File-like object. + + """This returns the file descriptor of the pty for the child. + """ + + return self.child_fd + + def close (self, force=True): # File-like object. + + """This closes the connection with the child application. Note that + calling close() more than once is valid. This emulates standard Python + behavior with files. Set force to True if you want to make sure that + the child is terminated (SIGKILL is sent if the child ignores SIGHUP + and SIGINT). """ + + if not self.closed: + self.flush() + os.close (self.child_fd) + time.sleep(self.delayafterclose) # Give kernel time to update process status. + if self.isalive(): + if not self.terminate(force): + raise ExceptionPexpect ('close() could not terminate the child using terminate()') + self.child_fd = -1 + self.closed = True + #self.pid = None + + def flush (self): # File-like object. + + """This does nothing. It is here to support the interface for a + File-like object. """ + + pass + + def isatty (self): # File-like object. + + """This returns True if the file descriptor is open and connected to a + tty(-like) device, else False. """ + + return os.isatty(self.child_fd) + + def waitnoecho (self, timeout=-1): + + """This waits until the terminal ECHO flag is set False. This returns + True if the echo mode is off. This returns False if the ECHO flag was + not set False before the timeout. This can be used to detect when the + child is waiting for a password. Usually a child application will turn + off echo mode when it is waiting for the user to enter a password. For + example, instead of expecting the "password:" prompt you can wait for + the child to set ECHO off:: + + p = pexpect.spawn ('ssh user@example.com') + p.waitnoecho() + p.sendline(mypassword) + + If timeout is None then this method to block forever until ECHO flag is + False. + + """ + + if timeout == -1: + timeout = self.timeout + if timeout is not None: + end_time = time.time() + timeout + while True: + if not self.getecho(): + return True + if timeout < 0 and timeout is not None: + return False + if timeout is not None: + timeout = end_time - time.time() + time.sleep(0.1) + + def getecho (self): + + """This returns the terminal echo mode. This returns True if echo is + on or False if echo is off. Child applications that are expecting you + to enter a password often set ECHO False. See waitnoecho(). """ + + attr = termios.tcgetattr(self.child_fd) + if attr[3] & termios.ECHO: + return True + return False + + def setecho (self, state): + + """This sets the terminal echo mode on or off. Note that anything the + child sent before the echo will be lost, so you should be sure that + your input buffer is empty before you call setecho(). For example, the + following will work as expected:: + + p = pexpect.spawn('cat') + p.sendline ('1234') # We will see this twice (once from tty echo and again from cat). + p.expect (['1234']) + p.expect (['1234']) + p.setecho(False) # Turn off tty echo + p.sendline ('abcd') # We will set this only once (echoed by cat). + p.sendline ('wxyz') # We will set this only once (echoed by cat) + p.expect (['abcd']) + p.expect (['wxyz']) + + The following WILL NOT WORK because the lines sent before the setecho + will be lost:: + + p = pexpect.spawn('cat') + p.sendline ('1234') # We will see this twice (once from tty echo and again from cat). + p.setecho(False) # Turn off tty echo + p.sendline ('abcd') # We will set this only once (echoed by cat). + p.sendline ('wxyz') # We will set this only once (echoed by cat) + p.expect (['1234']) + p.expect (['1234']) + p.expect (['abcd']) + p.expect (['wxyz']) + """ + + self.child_fd + attr = termios.tcgetattr(self.child_fd) + if state: + attr[3] = attr[3] | termios.ECHO + else: + attr[3] = attr[3] & ~termios.ECHO + # I tried TCSADRAIN and TCSAFLUSH, but these were inconsistent + # and blocked on some platforms. TCSADRAIN is probably ideal if it worked. + termios.tcsetattr(self.child_fd, termios.TCSANOW, attr) + + def read_nonblocking (self, size = 1, timeout = -1): + + """This reads at most size characters from the child application. It + includes a timeout. If the read does not complete within the timeout + period then a TIMEOUT exception is raised. If the end of file is read + then an EOF exception will be raised. If a log file was set using + setlog() then all data will also be written to the log file. + + If timeout is None then the read may block indefinitely. If timeout is -1 + then the self.timeout value is used. If timeout is 0 then the child is + polled and if there was no data immediately ready then this will raise + a TIMEOUT exception. + + The timeout refers only to the amount of time to read at least one + character. This is not effected by the 'size' parameter, so if you call + read_nonblocking(size=100, timeout=30) and only one character is + available right away then one character will be returned immediately. + It will not wait for 30 seconds for another 99 characters to come in. + + This is a wrapper around os.read(). It uses select.select() to + implement the timeout. """ + + if self.closed: + raise ValueError ('I/O operation on closed file in read_nonblocking().') + + if timeout == -1: + timeout = self.timeout + + # Note that some systems such as Solaris do not give an EOF when + # the child dies. In fact, you can still try to read + # from the child_fd -- it will block forever or until TIMEOUT. + # For this case, I test isalive() before doing any reading. + # If isalive() is false, then I pretend that this is the same as EOF. + if not self.isalive(): + r,w,e = self.__select([self.child_fd], [], [], 0) # timeout of 0 means "poll" + if not r: + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Braindead platform.') + elif self.__irix_hack: + # This is a hack for Irix. It seems that Irix requires a long delay before checking isalive. + # This adds a 2 second delay, but only when the child is terminated. + r, w, e = self.__select([self.child_fd], [], [], 2) + if not r and not self.isalive(): + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Pokey platform.') + + r,w,e = self.__select([self.child_fd], [], [], timeout) + + if not r: + if not self.isalive(): + # Some platforms, such as Irix, will claim that their processes are alive; + # then timeout on the select; and then finally admit that they are not alive. + self.flag_eof = True + raise EOF ('End of File (EOF) in read_nonblocking(). Very pokey platform.') + else: + raise TIMEOUT ('Timeout exceeded in read_nonblocking().') + + if self.child_fd in r: + try: + s = os.read(self.child_fd, size) + except OSError, e: # Linux does this + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Exception style platform.') + if s == '': # BSD style + self.flag_eof = True + raise EOF ('End Of File (EOF) in read_nonblocking(). Empty string style platform.') + + if self.logfile is not None: + self.logfile.write (s) + self.logfile.flush() + if self.logfile_read is not None: + self.logfile_read.write (s) + self.logfile_read.flush() + + return s + + raise ExceptionPexpect ('Reached an unexpected state in read_nonblocking().') + + def read (self, size = -1): # File-like object. + + """This reads at most "size" bytes from the file (less if the read hits + EOF before obtaining size bytes). If the size argument is negative or + omitted, read all data until EOF is reached. The bytes are returned as + a string object. An empty string is returned when EOF is encountered + immediately. """ + + if size == 0: + return '' + if size < 0: + self.expect (self.delimiter) # delimiter default is EOF + return self.before + + # I could have done this more directly by not using expect(), but + # I deliberately decided to couple read() to expect() so that + # I would catch any bugs early and ensure consistant behavior. + # It's a little less efficient, but there is less for me to + # worry about if I have to later modify read() or expect(). + # Note, it's OK if size==-1 in the regex. That just means it + # will never match anything in which case we stop only on EOF. + cre = re.compile('.{%d}' % size, re.DOTALL) + index = self.expect ([cre, self.delimiter]) # delimiter default is EOF + if index == 0: + return self.after ### self.before should be ''. Should I assert this? + return self.before + + def readline (self, size = -1): # File-like object. + + """This reads and returns one entire line. A trailing newline is kept + in the string, but may be absent when a file ends with an incomplete + line. Note: This readline() looks for a \\r\\n pair even on UNIX + because this is what the pseudo tty device returns. So contrary to what + you may expect you will receive the newline as \\r\\n. An empty string + is returned when EOF is hit immediately. Currently, the size argument is + mostly ignored, so this behavior is not standard for a file-like + object. If size is 0 then an empty string is returned. """ + + if size == 0: + return '' + index = self.expect (['\r\n', self.delimiter]) # delimiter default is EOF + if index == 0: + return self.before + '\r\n' + else: + return self.before + + def __iter__ (self): # File-like object. + + """This is to support iterators over a file-like object. + """ + + return self + + def next (self): # File-like object. + + """This is to support iterators over a file-like object. + """ + + result = self.readline() + if result == "": + raise StopIteration + return result + + def readlines (self, sizehint = -1): # File-like object. + + """This reads until EOF using readline() and returns a list containing + the lines thus read. The optional "sizehint" argument is ignored. """ + + lines = [] + while True: + line = self.readline() + if not line: + break + lines.append(line) + return lines + + def write(self, s): # File-like object. + + """This is similar to send() except that there is no return value. + """ + + self.send (s) + + def writelines (self, sequence): # File-like object. + + """This calls write() for each element in the sequence. The sequence + can be any iterable object producing strings, typically a list of + strings. This does not add line separators There is no return value. + """ + + for s in sequence: + self.write (s) + + def send(self, s): + + """This sends a string to the child process. This returns the number of + bytes written. If a log file was set then the data is also written to + the log. """ + + time.sleep(self.delaybeforesend) + if self.logfile is not None: + self.logfile.write (s) + self.logfile.flush() + if self.logfile_send is not None: + self.logfile_send.write (s) + self.logfile_send.flush() + c = os.write(self.child_fd, s) + return c + + def sendline(self, s=''): + + """This is like send(), but it adds a line feed (os.linesep). This + returns the number of bytes written. """ + + n = self.send(s) + n = n + self.send (os.linesep) + return n + + def sendcontrol(self, char): + + """This sends a control character to the child such as Ctrl-C or + Ctrl-D. For example, to send a Ctrl-G (ASCII 7):: + + child.sendcontrol('g') + + See also, sendintr() and sendeof(). + """ + + char = char.lower() + a = ord(char) + if a>=97 and a<=122: + a = a - ord('a') + 1 + return self.send (chr(a)) + d = {'@':0, '`':0, + '[':27, '{':27, + '\\':28, '|':28, + ']':29, '}': 29, + '^':30, '~':30, + '_':31, + '?':127} + if char not in d: + return 0 + return self.send (chr(d[char])) + + def sendeof(self): + + """This sends an EOF to the child. This sends a character which causes + the pending parent output buffer to be sent to the waiting child + program without waiting for end-of-line. If it is the first character + of the line, the read() in the user program returns 0, which signifies + end-of-file. This means to work as expected a sendeof() has to be + called at the beginning of a line. This method does not send a newline. + It is the responsibility of the caller to ensure the eof is sent at the + beginning of a line. """ + + ### Hmmm... how do I send an EOF? + ###C if ((m = write(pty, *buf, p - *buf)) < 0) + ###C return (errno == EWOULDBLOCK) ? n : -1; + #fd = sys.stdin.fileno() + #old = termios.tcgetattr(fd) # remember current state + #attr = termios.tcgetattr(fd) + #attr[3] = attr[3] | termios.ICANON # ICANON must be set to recognize EOF + #try: # use try/finally to ensure state gets restored + # termios.tcsetattr(fd, termios.TCSADRAIN, attr) + # if hasattr(termios, 'CEOF'): + # os.write (self.child_fd, '%c' % termios.CEOF) + # else: + # # Silly platform does not define CEOF so assume CTRL-D + # os.write (self.child_fd, '%c' % 4) + #finally: # restore state + # termios.tcsetattr(fd, termios.TCSADRAIN, old) + if hasattr(termios, 'VEOF'): + char = termios.tcgetattr(self.child_fd)[6][termios.VEOF] + else: + # platform does not define VEOF so assume CTRL-D + char = chr(4) + self.send(char) + + def sendintr(self): + + """This sends a SIGINT to the child. It does not require + the SIGINT to be the first character on a line. """ + + if hasattr(termios, 'VINTR'): + char = termios.tcgetattr(self.child_fd)[6][termios.VINTR] + else: + # platform does not define VINTR so assume CTRL-C + char = chr(3) + self.send (char) + + def eof (self): + + """This returns True if the EOF exception was ever raised. + """ + + return self.flag_eof + + def terminate(self, force=False): + + """This forces a child process to terminate. It starts nicely with + SIGHUP and SIGINT. If "force" is True then moves onto SIGKILL. This + returns True if the child was terminated. This returns False if the + child could not be terminated. """ + + if not self.isalive(): + return True + try: + self.kill(signal.SIGHUP) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGCONT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + self.kill(signal.SIGINT) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + if force: + self.kill(signal.SIGKILL) + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + return False + except OSError, e: + # I think there are kernel timing issues that sometimes cause + # this to happen. I think isalive() reports True, but the + # process is dead to the kernel. + # Make one last attempt to see if the kernel is up to date. + time.sleep(self.delayafterterminate) + if not self.isalive(): + return True + else: + return False + + def wait(self): + + """This waits until the child exits. This is a blocking call. This will + not read any data from the child, so this will block forever if the + child has unread output and has terminated. In other words, the child + may have printed output then called exit(); but, technically, the child + is still alive until its output is read. """ + + if self.isalive(): + pid, status = os.waitpid(self.pid, 0) + else: + raise ExceptionPexpect ('Cannot wait for dead child process.') + self.exitstatus = os.WEXITSTATUS(status) + if os.WIFEXITED (status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED (status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED (status): + raise ExceptionPexpect ('Wait was called for a child process that is stopped. This is not supported. Is some other process attempting job control with our child pid?') + return self.exitstatus + + def isalive(self): + + """This tests if the child process is running or not. This is + non-blocking. If the child was terminated then this will read the + exitstatus or signalstatus of the child. This returns True if the child + process appears to be running or False if not. It can take literally + SECONDS for Solaris to return the right status. """ + + if self.terminated: + return False + + if self.flag_eof: + # This is for Linux, which requires the blocking form of waitpid to get + # status of a defunct process. This is super-lame. The flag_eof would have + # been set in read_nonblocking(), so this should be safe. + waitpid_options = 0 + else: + waitpid_options = os.WNOHANG + + try: + pid, status = os.waitpid(self.pid, waitpid_options) + except OSError, e: # No child processes + if e[0] == errno.ECHILD: + raise ExceptionPexpect ('isalive() encountered condition where "terminated" is 0, but there was no child process. Did someone else call waitpid() on our process?') + else: + raise e + + # I have to do this twice for Solaris. I can't even believe that I figured this out... + # If waitpid() returns 0 it means that no child process wishes to + # report, and the value of status is undefined. + if pid == 0: + try: + pid, status = os.waitpid(self.pid, waitpid_options) ### os.WNOHANG) # Solaris! + except OSError, e: # This should never happen... + if e[0] == errno.ECHILD: + raise ExceptionPexpect ('isalive() encountered condition that should never happen. There was no child process. Did someone else call waitpid() on our process?') + else: + raise e + + # If pid is still 0 after two calls to waitpid() then + # the process really is alive. This seems to work on all platforms, except + # for Irix which seems to require a blocking call on waitpid or select, so I let read_nonblocking + # take care of this situation (unfortunately, this requires waiting through the timeout). + if pid == 0: + return True + + if pid == 0: + return True + + if os.WIFEXITED (status): + self.status = status + self.exitstatus = os.WEXITSTATUS(status) + self.signalstatus = None + self.terminated = True + elif os.WIFSIGNALED (status): + self.status = status + self.exitstatus = None + self.signalstatus = os.WTERMSIG(status) + self.terminated = True + elif os.WIFSTOPPED (status): + raise ExceptionPexpect ('isalive() encountered condition where child process is stopped. This is not supported. Is some other process attempting job control with our child pid?') + return False + + def kill(self, sig): + + """This sends the given signal to the child application. In keeping + with UNIX tradition it has a misleading name. It does not necessarily + kill the child unless you send the right signal. """ + + # Same as os.kill, but the pid is given for you. + if self.isalive(): + os.kill(self.pid, sig) + + def compile_pattern_list(self, patterns): + + """This compiles a pattern-string or a list of pattern-strings. + Patterns must be a StringType, EOF, TIMEOUT, SRE_Pattern, or a list of + those. Patterns may also be None which results in an empty list (you + might do this if waiting for an EOF or TIMEOUT condition without + expecting any pattern). + + This is used by expect() when calling expect_list(). Thus expect() is + nothing more than:: + + cpl = self.compile_pattern_list(pl) + return self.expect_list(cpl, timeout) + + If you are using expect() within a loop it may be more + efficient to compile the patterns first and then call expect_list(). + This avoid calls in a loop to compile_pattern_list():: + + cpl = self.compile_pattern_list(my_pattern) + while some_condition: + ... + i = self.expect_list(clp, timeout) + ... + """ + + if patterns is None: + return [] + if type(patterns) is not types.ListType: + patterns = [patterns] + + compile_flags = re.DOTALL # Allow dot to match \n + if self.ignorecase: + compile_flags = compile_flags | re.IGNORECASE + compiled_pattern_list = [] + for p in patterns: + if type(p) in types.StringTypes: + compiled_pattern_list.append(re.compile(p, compile_flags)) + elif p is EOF: + compiled_pattern_list.append(EOF) + elif p is TIMEOUT: + compiled_pattern_list.append(TIMEOUT) + elif type(p) is type(re.compile('')): + compiled_pattern_list.append(p) + else: + raise TypeError ('Argument must be one of StringTypes, EOF, TIMEOUT, SRE_Pattern, or a list of those type. %s' % str(type(p))) + + return compiled_pattern_list + + def expect(self, pattern, timeout = -1, searchwindowsize=-1): + + """This seeks through the stream until a pattern is matched. The + pattern is overloaded and may take several types. The pattern can be a + StringType, EOF, a compiled re, or a list of any of those types. + Strings will be compiled to re types. This returns the index into the + pattern list. If the pattern was not a list this returns index 0 on a + successful match. This may raise exceptions for EOF or TIMEOUT. To + avoid the EOF or TIMEOUT exceptions add EOF or TIMEOUT to the pattern + list. That will cause expect to match an EOF or TIMEOUT condition + instead of raising an exception. + + If you pass a list of patterns and more than one matches, the first match + in the stream is chosen. If more than one pattern matches at that point, + the leftmost in the pattern list is chosen. For example:: + + # the input is 'foobar' + index = p.expect (['bar', 'foo', 'foobar']) + # returns 1 ('foo') even though 'foobar' is a "better" match + + Please note, however, that buffering can affect this behavior, since + input arrives in unpredictable chunks. For example:: + + # the input is 'foobar' + index = p.expect (['foobar', 'foo']) + # returns 0 ('foobar') if all input is available at once, + # but returs 1 ('foo') if parts of the final 'bar' arrive late + + After a match is found the instance attributes 'before', 'after' and + 'match' will be set. You can see all the data read before the match in + 'before'. You can see the data that was matched in 'after'. The + re.MatchObject used in the re match will be in 'match'. If an error + occurred then 'before' will be set to all the data read so far and + 'after' and 'match' will be None. + + If timeout is -1 then timeout will be set to the self.timeout value. + + A list entry may be EOF or TIMEOUT instead of a string. This will + catch these exceptions and return the index of the list entry instead + of raising the exception. The attribute 'after' will be set to the + exception type. The attribute 'match' will be None. This allows you to + write code like this:: + + index = p.expect (['good', 'bad', pexpect.EOF, pexpect.TIMEOUT]) + if index == 0: + do_something() + elif index == 1: + do_something_else() + elif index == 2: + do_some_other_thing() + elif index == 3: + do_something_completely_different() + + instead of code like this:: + + try: + index = p.expect (['good', 'bad']) + if index == 0: + do_something() + elif index == 1: + do_something_else() + except EOF: + do_some_other_thing() + except TIMEOUT: + do_something_completely_different() + + These two forms are equivalent. It all depends on what you want. You + can also just expect the EOF if you are waiting for all output of a + child to finish. For example:: + + p = pexpect.spawn('/bin/ls') + p.expect (pexpect.EOF) + print p.before + + If you are trying to optimize for speed then see expect_list(). + """ + + compiled_pattern_list = self.compile_pattern_list(pattern) + return self.expect_list(compiled_pattern_list, timeout, searchwindowsize) + + def expect_list(self, pattern_list, timeout = -1, searchwindowsize = -1): + + """This takes a list of compiled regular expressions and returns the + index into the pattern_list that matched the child output. The list may + also contain EOF or TIMEOUT (which are not compiled regular + expressions). This method is similar to the expect() method except that + expect_list() does not recompile the pattern list on every call. This + may help if you are trying to optimize for speed, otherwise just use + the expect() method. This is called by expect(). If timeout==-1 then + the self.timeout value is used. If searchwindowsize==-1 then the + self.searchwindowsize value is used. """ + + return self.expect_loop(searcher_re(pattern_list), timeout, searchwindowsize) + + def expect_exact(self, pattern_list, timeout = -1, searchwindowsize = -1): + + """This is similar to expect(), but uses plain string matching instead + of compiled regular expressions in 'pattern_list'. The 'pattern_list' + may be a string; a list or other sequence of strings; or TIMEOUT and + EOF. + + This call might be faster than expect() for two reasons: string + searching is faster than RE matching and it is possible to limit the + search to just the end of the input buffer. + + This method is also useful when you don't want to have to worry about + escaping regular expression characters that you want to match.""" + + if type(pattern_list) in types.StringTypes or pattern_list in (TIMEOUT, EOF): + pattern_list = [pattern_list] + return self.expect_loop(searcher_string(pattern_list), timeout, searchwindowsize) + + def expect_loop(self, searcher, timeout = -1, searchwindowsize = -1): + + """This is the common loop used inside expect. The 'searcher' should be + an instance of searcher_re or searcher_string, which describes how and what + to search for in the input. + + See expect() for other arguments, return value and exceptions. """ + + self.searcher = searcher + + if timeout == -1: + timeout = self.timeout + if timeout is not None: + end_time = time.time() + timeout + if searchwindowsize == -1: + searchwindowsize = self.searchwindowsize + + try: + incoming = self.buffer + freshlen = len(incoming) + while True: # Keep reading until exception or return. + index = searcher.search(incoming, freshlen, searchwindowsize) + if index >= 0: + self.buffer = incoming[searcher.end : ] + self.before = incoming[ : searcher.start] + self.after = incoming[searcher.start : searcher.end] + self.match = searcher.match + self.match_index = index + return self.match_index + # No match at this point + if timeout < 0 and timeout is not None: + raise TIMEOUT ('Timeout exceeded in expect_any().') + # Still have time left, so read more data + c = self.read_nonblocking (self.maxread, timeout) + freshlen = len(c) + time.sleep (0.0001) + incoming = incoming + c + if timeout is not None: + timeout = end_time - time.time() + except EOF, e: + self.buffer = '' + self.before = incoming + self.after = EOF + index = searcher.eof_index + if index >= 0: + self.match = EOF + self.match_index = index + return self.match_index + else: + self.match = None + self.match_index = None + raise EOF (str(e) + '\n' + str(self)) + except TIMEOUT, e: + self.buffer = incoming + self.before = incoming + self.after = TIMEOUT + index = searcher.timeout_index + if index >= 0: + self.match = TIMEOUT + self.match_index = index + return self.match_index + else: + self.match = None + self.match_index = None + raise TIMEOUT (str(e) + '\n' + str(self)) + except: + self.before = incoming + self.after = None + self.match = None + self.match_index = None + raise + + def getwinsize(self): + + """This returns the terminal window size of the child tty. The return + value is a tuple of (rows, cols). """ + + TIOCGWINSZ = getattr(termios, 'TIOCGWINSZ', 1074295912L) + s = struct.pack('HHHH', 0, 0, 0, 0) + x = fcntl.ioctl(self.fileno(), TIOCGWINSZ, s) + return struct.unpack('HHHH', x)[0:2] + + def setwinsize(self, r, c): + + """This sets the terminal window size of the child tty. This will cause + a SIGWINCH signal to be sent to the child. This does not change the + physical window size. It changes the size reported to TTY-aware + applications like vi or curses -- applications that respond to the + SIGWINCH signal. """ + + # Some very old platforms have a bug that causes the value for + # termios.TIOCSWINSZ to be truncated. There was a hack here to work + # around this, but it caused problems with newer platforms so has been + # removed. For details see https://github.com/pexpect/pexpect/issues/39 + TIOCSWINSZ = getattr(termios, 'TIOCSWINSZ', -2146929561) + # Note, assume ws_xpixel and ws_ypixel are zero. + s = struct.pack('HHHH', r, c, 0, 0) + fcntl.ioctl(self.fileno(), TIOCSWINSZ, s) + + def interact(self, escape_character = chr(29), input_filter = None, output_filter = None): + + """This gives control of the child process to the interactive user (the + human at the keyboard). Keystrokes are sent to the child process, and + the stdout and stderr output of the child process is printed. This + simply echos the child stdout and child stderr to the real stdout and + it echos the real stdin to the child stdin. When the user types the + escape_character this method will stop. The default for + escape_character is ^]. This should not be confused with ASCII 27 -- + the ESC character. ASCII 29 was chosen for historical merit because + this is the character used by 'telnet' as the escape character. The + escape_character will not be sent to the child process. + + You may pass in optional input and output filter functions. These + functions should take a string and return a string. The output_filter + will be passed all the output from the child process. The input_filter + will be passed all the keyboard input from the user. The input_filter + is run BEFORE the check for the escape_character. + + Note that if you change the window size of the parent the SIGWINCH + signal will not be passed through to the child. If you want the child + window size to change when the parent's window size changes then do + something like the following example:: + + import pexpect, struct, fcntl, termios, signal, sys + def sigwinch_passthrough (sig, data): + s = struct.pack("HHHH", 0, 0, 0, 0) + a = struct.unpack('hhhh', fcntl.ioctl(sys.stdout.fileno(), termios.TIOCGWINSZ , s)) + global p + p.setwinsize(a[0],a[1]) + p = pexpect.spawn('/bin/bash') # Note this is global and used in sigwinch_passthrough. + signal.signal(signal.SIGWINCH, sigwinch_passthrough) + p.interact() + """ + + # Flush the buffer. + self.stdout.write (self.buffer) + self.stdout.flush() + self.buffer = '' + mode = tty.tcgetattr(self.STDIN_FILENO) + tty.setraw(self.STDIN_FILENO) + try: + self.__interact_copy(escape_character, input_filter, output_filter) + finally: + tty.tcsetattr(self.STDIN_FILENO, tty.TCSAFLUSH, mode) + + def __interact_writen(self, fd, data): + + """This is used by the interact() method. + """ + + while data != '' and self.isalive(): + n = os.write(fd, data) + data = data[n:] + + def __interact_read(self, fd): + + """This is used by the interact() method. + """ + + return os.read(fd, 1000) + + def __interact_copy(self, escape_character = None, input_filter = None, output_filter = None): + + """This is used by the interact() method. + """ + + while self.isalive(): + r,w,e = self.__select([self.child_fd, self.STDIN_FILENO], [], []) + if self.child_fd in r: + data = self.__interact_read(self.child_fd) + if output_filter: data = output_filter(data) + if self.logfile is not None: + self.logfile.write (data) + self.logfile.flush() + os.write(self.STDOUT_FILENO, data) + if self.STDIN_FILENO in r: + data = self.__interact_read(self.STDIN_FILENO) + if input_filter: data = input_filter(data) + i = data.rfind(escape_character) + if i != -1: + data = data[:i] + self.__interact_writen(self.child_fd, data) + break + self.__interact_writen(self.child_fd, data) + + def __select (self, iwtd, owtd, ewtd, timeout=None): + + """This is a wrapper around select.select() that ignores signals. If + select.select raises a select.error exception and errno is an EINTR + error then it is ignored. Mainly this is used to ignore sigwinch + (terminal resize). """ + + # if select() is interrupted by a signal (errno==EINTR) then + # we loop back and enter the select() again. + if timeout is not None: + end_time = time.time() + timeout + while True: + try: + return select.select (iwtd, owtd, ewtd, timeout) + except select.error, e: + if e[0] == errno.EINTR: + # if we loop back we have to subtract the amount of time we already waited. + if timeout is not None: + timeout = end_time - time.time() + if timeout < 0: + return ([],[],[]) + else: # something else caused the select.error, so this really is an exception + raise + +############################################################################## +# The following methods are no longer supported or allowed. + + def setmaxread (self, maxread): + + """This method is no longer supported or allowed. I don't like getters + and setters without a good reason. """ + + raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the maxread member variable.') + + def setlog (self, fileobject): + + """This method is no longer supported or allowed. + """ + + raise ExceptionPexpect ('This method is no longer supported or allowed. Just assign a value to the logfile member variable.') + +############################################################################## +# End of spawn class +############################################################################## + +class searcher_string (object): + + """This is a plain string search helper for the spawn.expect_any() method. + + Attributes: + + eof_index - index of EOF, or -1 + timeout_index - index of TIMEOUT, or -1 + + After a successful match by the search() method the following attributes + are available: + + start - index into the buffer, first byte of match + end - index into the buffer, first byte after match + match - the matching string itself + """ + + def __init__(self, strings): + + """This creates an instance of searcher_string. This argument 'strings' + may be a list; a sequence of strings; or the EOF or TIMEOUT types. """ + + self.eof_index = -1 + self.timeout_index = -1 + self._strings = [] + for n, s in zip(range(len(strings)), strings): + if s is EOF: + self.eof_index = n + continue + if s is TIMEOUT: + self.timeout_index = n + continue + self._strings.append((n, s)) + + def __str__(self): + + """This returns a human-readable string that represents the state of + the object.""" + + ss = [ (ns[0],' %d: "%s"' % ns) for ns in self._strings ] + ss.append((-1,'searcher_string:')) + if self.eof_index >= 0: + ss.append ((self.eof_index,' %d: EOF' % self.eof_index)) + if self.timeout_index >= 0: + ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index)) + ss.sort() + ss = zip(*ss)[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): + + """This searches 'buffer' for the first occurence of one of the search + strings. 'freshlen' must indicate the number of bytes at the end of + 'buffer' which have not been searched before. It helps to avoid + searching the same, possibly big, buffer over and over again. + + See class spawn for the 'searchwindowsize' argument. + + If there is a match this returns the index of that string, and sets + 'start', 'end' and 'match'. Otherwise, this returns -1. """ + + absurd_match = len(buffer) + first_match = absurd_match + + # 'freshlen' helps a lot here. Further optimizations could + # possibly include: + # + # using something like the Boyer-Moore Fast String Searching + # Algorithm; pre-compiling the search through a list of + # strings into something that can scan the input once to + # search for all N strings; realize that if we search for + # ['bar', 'baz'] and the input is '...foo' we need not bother + # rescanning until we've read three more bytes. + # + # Sadly, I don't know enough about this interesting topic. /grahn + + for index, s in self._strings: + if searchwindowsize is None: + # the match, if any, can only be in the fresh data, + # or at the very end of the old data + offset = -(freshlen+len(s)) + else: + # better obey searchwindowsize + offset = -searchwindowsize + n = buffer.find(s, offset) + if n >= 0 and n < first_match: + first_match = n + best_index, best_match = index, s + if first_match == absurd_match: + return -1 + self.match = best_match + self.start = first_match + self.end = self.start + len(self.match) + return best_index + +class searcher_re (object): + + """This is regular expression string search helper for the + spawn.expect_any() method. + + Attributes: + + eof_index - index of EOF, or -1 + timeout_index - index of TIMEOUT, or -1 + + After a successful match by the search() method the following attributes + are available: + + start - index into the buffer, first byte of match + end - index into the buffer, first byte after match + match - the re.match object returned by a succesful re.search + + """ + + def __init__(self, patterns): + + """This creates an instance that searches for 'patterns' Where + 'patterns' may be a list or other sequence of compiled regular + expressions, or the EOF or TIMEOUT types.""" + + self.eof_index = -1 + self.timeout_index = -1 + self._searches = [] + for n, s in zip(range(len(patterns)), patterns): + if s is EOF: + self.eof_index = n + continue + if s is TIMEOUT: + self.timeout_index = n + continue + self._searches.append((n, s)) + + def __str__(self): + + """This returns a human-readable string that represents the state of + the object.""" + + ss = [ (n,' %d: re.compile("%s")' % (n,str(s.pattern))) for n,s in self._searches] + ss.append((-1,'searcher_re:')) + if self.eof_index >= 0: + ss.append ((self.eof_index,' %d: EOF' % self.eof_index)) + if self.timeout_index >= 0: + ss.append ((self.timeout_index,' %d: TIMEOUT' % self.timeout_index)) + ss.sort() + ss = zip(*ss)[1] + return '\n'.join(ss) + + def search(self, buffer, freshlen, searchwindowsize=None): + + """This searches 'buffer' for the first occurence of one of the regular + expressions. 'freshlen' must indicate the number of bytes at the end of + 'buffer' which have not been searched before. + + See class spawn for the 'searchwindowsize' argument. + + If there is a match this returns the index of that string, and sets + 'start', 'end' and 'match'. Otherwise, returns -1.""" + + absurd_match = len(buffer) + first_match = absurd_match + # 'freshlen' doesn't help here -- we cannot predict the + # length of a match, and the re module provides no help. + if searchwindowsize is None: + searchstart = 0 + else: + searchstart = max(0, len(buffer)-searchwindowsize) + for index, s in self._searches: + match = s.search(buffer, searchstart) + if match is None: + continue + n = match.start() + if n < first_match: + first_match = n + the_match = match + best_index = index + if first_match == absurd_match: + return -1 + self.start = first_match + self.match = the_match + self.end = self.match.end() + return best_index + +def which (filename): + + """This takes a given filename; tries to find it in the environment path; + then checks if it is executable. This returns the full path to the filename + if found and executable. Otherwise this returns None.""" + + # Special case where filename already contains a path. + if os.path.dirname(filename) != '': + if os.access (filename, os.X_OK): + return filename + + if not os.environ.has_key('PATH') or os.environ['PATH'] == '': + p = os.defpath + else: + p = os.environ['PATH'] + + # Oddly enough this was the one line that made Pexpect + # incompatible with Python 1.5.2. + #pathlist = p.split (os.pathsep) + pathlist = string.split (p, os.pathsep) + + for path in pathlist: + f = os.path.join(path, filename) + if os.access(f, os.X_OK): + return f + return None + +def split_command_line(command_line): + + """This splits a command line into a list of arguments. It splits arguments + on spaces, but handles embedded quotes, doublequotes, and escaped + characters. It's impossible to do this with a regular expression, so I + wrote a little state machine to parse the command line. """ + + arg_list = [] + arg = '' + + # Constants to name the states we can be in. + state_basic = 0 + state_esc = 1 + state_singlequote = 2 + state_doublequote = 3 + state_whitespace = 4 # The state of consuming whitespace between commands. + state = state_basic + + for c in command_line: + if state == state_basic or state == state_whitespace: + if c == '\\': # Escape the next character + state = state_esc + elif c == r"'": # Handle single quote + state = state_singlequote + elif c == r'"': # Handle double quote + state = state_doublequote + elif c.isspace(): + # Add arg to arg_list if we aren't in the middle of whitespace. + if state == state_whitespace: + None # Do nothing. + else: + arg_list.append(arg) + arg = '' + state = state_whitespace + else: + arg = arg + c + state = state_basic + elif state == state_esc: + arg = arg + c + state = state_basic + elif state == state_singlequote: + if c == r"'": + state = state_basic + else: + arg = arg + c + elif state == state_doublequote: + if c == r'"': + state = state_basic + else: + arg = arg + c + + if arg != '': + arg_list.append(arg) + return arg_list + +# vi:ts=4:sw=4:expandtab:ft=python: diff --git a/third_party/Python/module/pexpect-2.4/pxssh.py b/third_party/Python/module/pexpect-2.4/pxssh.py new file mode 100644 index 000000000000..04ba25cbff5b --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/pxssh.py @@ -0,0 +1,311 @@ +"""This class extends pexpect.spawn to specialize setting up SSH connections. +This adds methods for login, logout, and expecting the shell prompt. + +$Id: pxssh.py 513 2008-02-09 18:26:13Z noah $ +""" + +from pexpect import * +import pexpect +import time + +__all__ = ['ExceptionPxssh', 'pxssh'] + +# Exception classes used by this module. +class ExceptionPxssh(ExceptionPexpect): + """Raised for pxssh exceptions. + """ + +class pxssh (spawn): + + """This class extends pexpect.spawn to specialize setting up SSH + connections. This adds methods for login, logout, and expecting the shell + prompt. It does various tricky things to handle many situations in the SSH + login process. For example, if the session is your first login, then pxssh + automatically accepts the remote certificate; or if you have public key + authentication setup then pxssh won't wait for the password prompt. + + pxssh uses the shell prompt to synchronize output from the remote host. In + order to make this more robust it sets the shell prompt to something more + unique than just $ or #. This should work on most Borne/Bash or Csh style + shells. + + Example that runs a few commands on a remote server and prints the result:: + + import pxssh + import getpass + try: + s = pxssh.pxssh() + hostname = raw_input('hostname: ') + username = raw_input('username: ') + password = getpass.getpass('password: ') + s.login (hostname, username, password) + s.sendline ('uptime') # run a command + s.prompt() # match the prompt + print s.before # print everything before the prompt. + s.sendline ('ls -l') + s.prompt() + print s.before + s.sendline ('df') + s.prompt() + print s.before + s.logout() + except pxssh.ExceptionPxssh, e: + print "pxssh failed on login." + print str(e) + + Note that if you have ssh-agent running while doing development with pxssh + then this can lead to a lot of confusion. Many X display managers (xdm, + gdm, kdm, etc.) will automatically start a GUI agent. You may see a GUI + dialog box popup asking for a password during development. You should turn + off any key agents during testing. The 'force_password' attribute will turn + off public key authentication. This will only work if the remote SSH server + is configured to allow password logins. Example of using 'force_password' + attribute:: + + s = pxssh.pxssh() + s.force_password = True + hostname = raw_input('hostname: ') + username = raw_input('username: ') + password = getpass.getpass('password: ') + s.login (hostname, username, password) + """ + + def __init__ (self, timeout=30, maxread=2000, searchwindowsize=None, logfile=None, cwd=None, env=None): + spawn.__init__(self, None, timeout=timeout, maxread=maxread, searchwindowsize=searchwindowsize, logfile=logfile, cwd=cwd, env=env) + + self.name = '<pxssh>' + + #SUBTLE HACK ALERT! Note that the command to set the prompt uses a + #slightly different string than the regular expression to match it. This + #is because when you set the prompt the command will echo back, but we + #don't want to match the echoed command. So if we make the set command + #slightly different than the regex we eliminate the problem. To make the + #set command different we add a backslash in front of $. The $ doesn't + #need to be escaped, but it doesn't hurt and serves to make the set + #prompt command different than the regex. + + # used to match the command-line prompt + self.UNIQUE_PROMPT = "\[PEXPECT\][\$\#] " + self.PROMPT = self.UNIQUE_PROMPT + + # used to set shell command-line prompt to UNIQUE_PROMPT. + self.PROMPT_SET_SH = "PS1='[PEXPECT]\$ '" + self.PROMPT_SET_CSH = "set prompt='[PEXPECT]\$ '" + self.SSH_OPTS = "-o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" + # Disabling X11 forwarding gets rid of the annoying SSH_ASKPASS from + # displaying a GUI password dialog. I have not figured out how to + # disable only SSH_ASKPASS without also disabling X11 forwarding. + # Unsetting SSH_ASKPASS on the remote side doesn't disable it! Annoying! + #self.SSH_OPTS = "-x -o'RSAAuthentication=no' -o 'PubkeyAuthentication=no'" + self.force_password = False + self.auto_prompt_reset = True + + def levenshtein_distance(self, a,b): + + """This calculates the Levenshtein distance between a and b. + """ + + n, m = len(a), len(b) + if n > m: + a,b = b,a + n,m = m,n + current = range(n+1) + for i in range(1,m+1): + previous, current = current, [i]+[0]*n + for j in range(1,n+1): + add, delete = previous[j]+1, current[j-1]+1 + change = previous[j-1] + if a[j-1] != b[i-1]: + change = change + 1 + current[j] = min(add, delete, change) + return current[n] + + def sync_original_prompt (self): + + """This attempts to find the prompt. Basically, press enter and record + the response; press enter again and record the response; if the two + responses are similar then assume we are at the original prompt. This + is a slow function. It can take over 10 seconds. """ + + # All of these timing pace values are magic. + # I came up with these based on what seemed reliable for + # connecting to a heavily loaded machine I have. + # If latency is worse than these values then this will fail. + + try: + self.read_nonblocking(size=10000,timeout=1) # GAS: Clear out the cache before getting the prompt + except TIMEOUT: + pass + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + x = self.read_nonblocking(size=1000,timeout=1) + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + a = self.read_nonblocking(size=1000,timeout=1) + time.sleep(0.1) + self.sendline() + time.sleep(0.5) + b = self.read_nonblocking(size=1000,timeout=1) + ld = self.levenshtein_distance(a,b) + len_a = len(a) + if len_a == 0: + return False + if float(ld)/len_a < 0.4: + return True + return False + + ### TODO: This is getting messy and I'm pretty sure this isn't perfect. + ### TODO: I need to draw a flow chart for this. + def login (self,server,username,password='',terminal_type='ansi',original_prompt=r"[#$]",login_timeout=10,port=None,auto_prompt_reset=True): + + """This logs the user into the given server. It uses the + 'original_prompt' to try to find the prompt right after login. When it + finds the prompt it immediately tries to reset the prompt to something + more easily matched. The default 'original_prompt' is very optimistic + and is easily fooled. It's more reliable to try to match the original + prompt as exactly as possible to prevent false matches by server + strings such as the "Message Of The Day". On many systems you can + disable the MOTD on the remote server by creating a zero-length file + called "~/.hushlogin" on the remote server. If a prompt cannot be found + then this will not necessarily cause the login to fail. In the case of + a timeout when looking for the prompt we assume that the original + prompt was so weird that we could not match it, so we use a few tricks + to guess when we have reached the prompt. Then we hope for the best and + blindly try to reset the prompt to something more unique. If that fails + then login() raises an ExceptionPxssh exception. + + In some situations it is not possible or desirable to reset the + original prompt. In this case, set 'auto_prompt_reset' to False to + inhibit setting the prompt to the UNIQUE_PROMPT. Remember that pxssh + uses a unique prompt in the prompt() method. If the original prompt is + not reset then this will disable the prompt() method unless you + manually set the PROMPT attribute. """ + + ssh_options = '-q' + if self.force_password: + ssh_options = ssh_options + ' ' + self.SSH_OPTS + if port is not None: + ssh_options = ssh_options + ' -p %s'%(str(port)) + cmd = "ssh %s -l %s %s" % (ssh_options, username, server) + + # This does not distinguish between a remote server 'password' prompt + # and a local ssh 'passphrase' prompt (for unlocking a private key). + spawn._spawn(self, cmd) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT, "(?i)connection closed by remote host"], timeout=login_timeout) + + # First phase + if i==0: + # New certificate -- always accept it. + # This is what you get if SSH does not have the remote host's + # public key stored in the 'known_hosts' cache. + self.sendline("yes") + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + if i==2: # password or passphrase + self.sendline(password) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + if i==4: + self.sendline(terminal_type) + i = self.expect(["(?i)are you sure you want to continue connecting", original_prompt, "(?i)(?:password)|(?:passphrase for key)", "(?i)permission denied", "(?i)terminal type", TIMEOUT]) + + # Second phase + if i==0: + # This is weird. This should not happen twice in a row. + self.close() + raise ExceptionPxssh ('Weird error. Got "are you sure" prompt twice.') + elif i==1: # can occur if you have a public key pair set to authenticate. + ### TODO: May NOT be OK if expect() got tricked and matched a false prompt. + pass + elif i==2: # password prompt again + # For incorrect passwords, some ssh servers will + # ask for the password again, others return 'denied' right away. + # If we get the password prompt again then this means + # we didn't get the password right the first time. + self.close() + raise ExceptionPxssh ('password refused') + elif i==3: # permission denied -- password was bad. + self.close() + raise ExceptionPxssh ('permission denied') + elif i==4: # terminal type again? WTF? + self.close() + raise ExceptionPxssh ('Weird error. Got "terminal type" prompt twice.') + elif i==5: # Timeout + #This is tricky... I presume that we are at the command-line prompt. + #It may be that the shell prompt was so weird that we couldn't match + #it. Or it may be that we couldn't log in for some other reason. I + #can't be sure, but it's safe to guess that we did login because if + #I presume wrong and we are not logged in then this should be caught + #later when I try to set the shell prompt. + pass + elif i==6: # Connection closed by remote host + self.close() + raise ExceptionPxssh ('connection closed') + else: # Unexpected + self.close() + raise ExceptionPxssh ('unexpected login response') + if not self.sync_original_prompt(): + self.close() + raise ExceptionPxssh ('could not synchronize with original prompt') + # We appear to be in. + # set shell prompt to something unique. + if auto_prompt_reset: + if not self.set_unique_prompt(): + self.close() + raise ExceptionPxssh ('could not set shell prompt\n'+self.before) + return True + + def logout (self): + + """This sends exit to the remote shell. If there are stopped jobs then + this automatically sends exit twice. """ + + self.sendline("exit") + index = self.expect([EOF, "(?i)there are stopped jobs"]) + if index==1: + self.sendline("exit") + self.expect(EOF) + self.close() + + def prompt (self, timeout=20): + + """This matches the shell prompt. This is little more than a short-cut + to the expect() method. This returns True if the shell prompt was + matched. This returns False if there was a timeout. Note that if you + called login() with auto_prompt_reset set to False then you should have + manually set the PROMPT attribute to a regex pattern for matching the + prompt. """ + + i = self.expect([self.PROMPT, TIMEOUT], timeout=timeout) + if i==1: + return False + return True + + def set_unique_prompt (self): + + """This sets the remote prompt to something more unique than # or $. + This makes it easier for the prompt() method to match the shell prompt + unambiguously. This method is called automatically by the login() + method, but you may want to call it manually if you somehow reset the + shell prompt. For example, if you 'su' to a different user then you + will need to manually reset the prompt. This sends shell commands to + the remote host to set the prompt, so this assumes the remote host is + ready to receive commands. + + Alternatively, you may use your own prompt pattern. Just set the PROMPT + attribute to a regular expression that matches it. In this case you + should call login() with auto_prompt_reset=False; then set the PROMPT + attribute. After that the prompt() method will try to match your prompt + pattern.""" + + self.sendline ("unset PROMPT_COMMAND") + self.sendline (self.PROMPT_SET_SH) # sh-style + i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) + if i == 0: # csh-style + self.sendline (self.PROMPT_SET_CSH) + i = self.expect ([TIMEOUT, self.PROMPT], timeout=10) + if i == 0: + return False + return True + +# vi:ts=4:sw=4:expandtab:ft=python: diff --git a/third_party/Python/module/pexpect-2.4/screen.py b/third_party/Python/module/pexpect-2.4/screen.py new file mode 100644 index 000000000000..13699f93b5b5 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/screen.py @@ -0,0 +1,380 @@ +"""This implements a virtual screen. This is used to support ANSI terminal +emulation. The screen representation and state is implemented in this class. +Most of the methods are inspired by ANSI screen control codes. The ANSI class +extends this class to add parsing of ANSI escape codes. + +$Id: screen.py 486 2007-07-13 01:04:16Z noah $ +""" + +import copy + +NUL = 0 # Fill character; ignored on input. +ENQ = 5 # Transmit answerback message. +BEL = 7 # Ring the bell. +BS = 8 # Move cursor left. +HT = 9 # Move cursor to next tab stop. +LF = 10 # Line feed. +VT = 11 # Same as LF. +FF = 12 # Same as LF. +CR = 13 # Move cursor to left margin or newline. +SO = 14 # Invoke G1 character set. +SI = 15 # Invoke G0 character set. +XON = 17 # Resume transmission. +XOFF = 19 # Halt transmission. +CAN = 24 # Cancel escape sequence. +SUB = 26 # Same as CAN. +ESC = 27 # Introduce a control sequence. +DEL = 127 # Fill character; ignored on input. +SPACE = chr(32) # Space or blank character. + +def constrain (n, min, max): + + """This returns a number, n constrained to the min and max bounds. """ + + if n < min: + return min + if n > max: + return max + return n + +class screen: + + """This object maintains the state of a virtual text screen as a + rectangluar array. This maintains a virtual cursor position and handles + scrolling as characters are added. This supports most of the methods needed + by an ANSI text screen. Row and column indexes are 1-based (not zero-based, + like arrays). """ + + def __init__ (self, r=24,c=80): + + """This initializes a blank scree of the given dimentions.""" + + self.rows = r + self.cols = c + self.cur_r = 1 + self.cur_c = 1 + self.cur_saved_r = 1 + self.cur_saved_c = 1 + self.scroll_row_start = 1 + self.scroll_row_end = self.rows + self.w = [ [SPACE] * self.cols for c in range(self.rows)] + + def __str__ (self): + + """This returns a printable representation of the screen. The end of + each screen line is terminated by a newline. """ + + return '\n'.join ([ ''.join(c) for c in self.w ]) + + def dump (self): + + """This returns a copy of the screen as a string. This is similar to + __str__ except that lines are not terminated with line feeds. """ + + return ''.join ([ ''.join(c) for c in self.w ]) + + def pretty (self): + + """This returns a copy of the screen as a string with an ASCII text box + around the screen border. This is similar to __str__ except that it + adds a box. """ + + top_bot = '+' + '-'*self.cols + '+\n' + return top_bot + '\n'.join(['|'+line+'|' for line in str(self).split('\n')]) + '\n' + top_bot + + def fill (self, ch=SPACE): + + self.fill_region (1,1,self.rows,self.cols, ch) + + def fill_region (self, rs,cs, re,ce, ch=SPACE): + + rs = constrain (rs, 1, self.rows) + re = constrain (re, 1, self.rows) + cs = constrain (cs, 1, self.cols) + ce = constrain (ce, 1, self.cols) + if rs > re: + rs, re = re, rs + if cs > ce: + cs, ce = ce, cs + for r in range (rs, re+1): + for c in range (cs, ce + 1): + self.put_abs (r,c,ch) + + def cr (self): + + """This moves the cursor to the beginning (col 1) of the current row. + """ + + self.cursor_home (self.cur_r, 1) + + def lf (self): + + """This moves the cursor down with scrolling. + """ + + old_r = self.cur_r + self.cursor_down() + if old_r == self.cur_r: + self.scroll_up () + self.erase_line() + + def crlf (self): + + """This advances the cursor with CRLF properties. + The cursor will line wrap and the screen may scroll. + """ + + self.cr () + self.lf () + + def newline (self): + + """This is an alias for crlf(). + """ + + self.crlf() + + def put_abs (self, r, c, ch): + + """Screen array starts at 1 index.""" + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + ch = str(ch)[0] + self.w[r-1][c-1] = ch + + def put (self, ch): + + """This puts a characters at the current cursor position. + """ + + self.put_abs (self.cur_r, self.cur_c, ch) + + def insert_abs (self, r, c, ch): + + """This inserts a character at (r,c). Everything under + and to the right is shifted right one character. + The last character of the line is lost. + """ + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + for ci in range (self.cols, c, -1): + self.put_abs (r,ci, self.get_abs(r,ci-1)) + self.put_abs (r,c,ch) + + def insert (self, ch): + + self.insert_abs (self.cur_r, self.cur_c, ch) + + def get_abs (self, r, c): + + r = constrain (r, 1, self.rows) + c = constrain (c, 1, self.cols) + return self.w[r-1][c-1] + + def get (self): + + self.get_abs (self.cur_r, self.cur_c) + + def get_region (self, rs,cs, re,ce): + + """This returns a list of lines representing the region. + """ + + rs = constrain (rs, 1, self.rows) + re = constrain (re, 1, self.rows) + cs = constrain (cs, 1, self.cols) + ce = constrain (ce, 1, self.cols) + if rs > re: + rs, re = re, rs + if cs > ce: + cs, ce = ce, cs + sc = [] + for r in range (rs, re+1): + line = '' + for c in range (cs, ce + 1): + ch = self.get_abs (r,c) + line = line + ch + sc.append (line) + return sc + + def cursor_constrain (self): + + """This keeps the cursor within the screen area. + """ + + self.cur_r = constrain (self.cur_r, 1, self.rows) + self.cur_c = constrain (self.cur_c, 1, self.cols) + + def cursor_home (self, r=1, c=1): # <ESC>[{ROW};{COLUMN}H + + self.cur_r = r + self.cur_c = c + self.cursor_constrain () + + def cursor_back (self,count=1): # <ESC>[{COUNT}D (not confused with down) + + self.cur_c = self.cur_c - count + self.cursor_constrain () + + def cursor_down (self,count=1): # <ESC>[{COUNT}B (not confused with back) + + self.cur_r = self.cur_r + count + self.cursor_constrain () + + def cursor_forward (self,count=1): # <ESC>[{COUNT}C + + self.cur_c = self.cur_c + count + self.cursor_constrain () + + def cursor_up (self,count=1): # <ESC>[{COUNT}A + + self.cur_r = self.cur_r - count + self.cursor_constrain () + + def cursor_up_reverse (self): # <ESC> M (called RI -- Reverse Index) + + old_r = self.cur_r + self.cursor_up() + if old_r == self.cur_r: + self.scroll_up() + + def cursor_force_position (self, r, c): # <ESC>[{ROW};{COLUMN}f + + """Identical to Cursor Home.""" + + self.cursor_home (r, c) + + def cursor_save (self): # <ESC>[s + + """Save current cursor position.""" + + self.cursor_save_attrs() + + def cursor_unsave (self): # <ESC>[u + + """Restores cursor position after a Save Cursor.""" + + self.cursor_restore_attrs() + + def cursor_save_attrs (self): # <ESC>7 + + """Save current cursor position.""" + + self.cur_saved_r = self.cur_r + self.cur_saved_c = self.cur_c + + def cursor_restore_attrs (self): # <ESC>8 + + """Restores cursor position after a Save Cursor.""" + + self.cursor_home (self.cur_saved_r, self.cur_saved_c) + + def scroll_constrain (self): + + """This keeps the scroll region within the screen region.""" + + if self.scroll_row_start <= 0: + self.scroll_row_start = 1 + if self.scroll_row_end > self.rows: + self.scroll_row_end = self.rows + + def scroll_screen (self): # <ESC>[r + + """Enable scrolling for entire display.""" + + self.scroll_row_start = 1 + self.scroll_row_end = self.rows + + def scroll_screen_rows (self, rs, re): # <ESC>[{start};{end}r + + """Enable scrolling from row {start} to row {end}.""" + + self.scroll_row_start = rs + self.scroll_row_end = re + self.scroll_constrain() + + def scroll_down (self): # <ESC>D + + """Scroll display down one line.""" + + # Screen is indexed from 1, but arrays are indexed from 0. + s = self.scroll_row_start - 1 + e = self.scroll_row_end - 1 + self.w[s+1:e+1] = copy.deepcopy(self.w[s:e]) + + def scroll_up (self): # <ESC>M + + """Scroll display up one line.""" + + # Screen is indexed from 1, but arrays are indexed from 0. + s = self.scroll_row_start - 1 + e = self.scroll_row_end - 1 + self.w[s:e] = copy.deepcopy(self.w[s+1:e+1]) + + def erase_end_of_line (self): # <ESC>[0K -or- <ESC>[K + + """Erases from the current cursor position to the end of the current + line.""" + + self.fill_region (self.cur_r, self.cur_c, self.cur_r, self.cols) + + def erase_start_of_line (self): # <ESC>[1K + + """Erases from the current cursor position to the start of the current + line.""" + + self.fill_region (self.cur_r, 1, self.cur_r, self.cur_c) + + def erase_line (self): # <ESC>[2K + + """Erases the entire current line.""" + + self.fill_region (self.cur_r, 1, self.cur_r, self.cols) + + def erase_down (self): # <ESC>[0J -or- <ESC>[J + + """Erases the screen from the current line down to the bottom of the + screen.""" + + self.erase_end_of_line () + self.fill_region (self.cur_r + 1, 1, self.rows, self.cols) + + def erase_up (self): # <ESC>[1J + + """Erases the screen from the current line up to the top of the + screen.""" + + self.erase_start_of_line () + self.fill_region (self.cur_r-1, 1, 1, self.cols) + + def erase_screen (self): # <ESC>[2J + + """Erases the screen with the background color.""" + + self.fill () + + def set_tab (self): # <ESC>H + + """Sets a tab at the current position.""" + + pass + + def clear_tab (self): # <ESC>[g + + """Clears tab at the current position.""" + + pass + + def clear_all_tabs (self): # <ESC>[3g + + """Clears all tabs.""" + + pass + +# Insert line Esc [ Pn L +# Delete line Esc [ Pn M +# Delete character Esc [ Pn P +# Scrolling region Esc [ Pn(top);Pn(bot) r + diff --git a/third_party/Python/module/pexpect-2.4/setup.py b/third_party/Python/module/pexpect-2.4/setup.py new file mode 100644 index 000000000000..81a442bed355 --- /dev/null +++ b/third_party/Python/module/pexpect-2.4/setup.py @@ -0,0 +1,39 @@ +''' +$Revision: 485 $ +$Date: 2007-07-12 15:23:15 -0700 (Thu, 12 Jul 2007) $ +''' +from distutils.core import setup +setup (name='pexpect', + version='2.4', + py_modules=['pexpect', 'pxssh', 'fdpexpect', 'FSM', 'screen', 'ANSI'], + description='Pexpect is a pure Python Expect. It allows easy control of other applications.', + author='Noah Spurrier', + author_email='noah@noah.org', + url='http://pexpect.sourceforge.net/', + license='MIT license', + platforms='UNIX', +) + +# classifiers = [ +# 'Development Status :: 4 - Beta', +# 'Environment :: Console', +# 'Environment :: Console (Text Based)', +# 'Intended Audience :: Developers', +# 'Intended Audience :: System Administrators', +# 'Intended Audience :: Quality Engineers', +# 'License :: OSI Approved :: Python Software Foundation License', +# 'Operating System :: POSIX', +# 'Operating System :: MacOS :: MacOS X', +# 'Programming Language :: Python', +# 'Topic :: Software Development', +# 'Topic :: Software Development :: Libraries :: Python Modules', +# 'Topic :: Software Development :: Quality Assurance', +# 'Topic :: Software Development :: Testing', +# 'Topic :: System, System :: Archiving :: Packaging, System :: Installation/Setup', +# 'Topic :: System :: Shells', +# 'Topic :: System :: Software Distribution', +# 'Topic :: Terminals, Utilities', +# ], + + + diff --git a/third_party/Python/module/progress/progress.py b/third_party/Python/module/progress/progress.py new file mode 100644 index 000000000000..734627d4b16e --- /dev/null +++ b/third_party/Python/module/progress/progress.py @@ -0,0 +1,154 @@ +#!/usr/bin/python + +from __future__ import print_function + +import use_lldb_suite +import six + +import sys +import time + +class ProgressBar(object): + """ProgressBar class holds the options of the progress bar. + The options are: + start State from which start the progress. For example, if start is + 5 and the end is 10, the progress of this state is 50% + end State in which the progress has terminated. + width -- + fill String to use for "filled" used to represent the progress + blank String to use for "filled" used to represent remaining space. + format Format + incremental + """ + light_block = six.unichr(0x2591).encode("utf-8") + solid_block = six.unichr(0x2588).encode("utf-8") + solid_right_arrow = six.unichr(0x25BA).encode("utf-8") + + def __init__(self, + start=0, + end=10, + width=12, + fill=six.unichr(0x25C9).encode("utf-8"), + blank=six.unichr(0x25CC).encode("utf-8"), + marker=six.unichr(0x25CE).encode("utf-8"), + format='[%(fill)s%(marker)s%(blank)s] %(progress)s%%', + incremental=True): + super(ProgressBar, self).__init__() + + self.start = start + self.end = end + self.width = width + self.fill = fill + self.blank = blank + self.marker = marker + self.format = format + self.incremental = incremental + self.step = 100 / float(width) #fix + self.reset() + + def __add__(self, increment): + increment = self._get_progress(increment) + if 100 > self.progress + increment: + self.progress += increment + else: + self.progress = 100 + return self + + def complete(self): + self.progress = 100 + return self + + def __str__(self): + progressed = int(self.progress / self.step) #fix + fill = progressed * self.fill + blank = (self.width - progressed) * self.blank + return self.format % {'fill': fill, 'blank': blank, 'marker': self.marker, 'progress': int(self.progress)} + + __repr__ = __str__ + + def _get_progress(self, increment): + return float(increment * 100) / self.end + + def reset(self): + """Resets the current progress to the start point""" + self.progress = self._get_progress(self.start) + return self + + +class AnimatedProgressBar(ProgressBar): + """Extends ProgressBar to allow you to use it straighforward on a script. + Accepts an extra keyword argument named `stdout` (by default use sys.stdout) + and may be any file-object to which send the progress status. + """ + def __init__(self, + start=0, + end=10, + width=12, + fill=six.unichr(0x25C9).encode("utf-8"), + blank=six.unichr(0x25CC).encode("utf-8"), + marker=six.unichr(0x25CE).encode("utf-8"), + format='[%(fill)s%(marker)s%(blank)s] %(progress)s%%', + incremental=True, + stdout=sys.stdout): + super(AnimatedProgressBar, self).__init__(start,end,width,fill,blank,marker,format,incremental) + self.stdout = stdout + + def show_progress(self): + if hasattr(self.stdout, 'isatty') and self.stdout.isatty(): + self.stdout.write('\r') + else: + self.stdout.write('\n') + self.stdout.write(str(self)) + self.stdout.flush() + +class ProgressWithEvents(AnimatedProgressBar): + """Extends AnimatedProgressBar to allow you to track a set of events that + cause the progress to move. For instance, in a deletion progress bar, you + can track files that were nuked and files that the user doesn't have access to + """ + def __init__(self, + start=0, + end=10, + width=12, + fill=six.unichr(0x25C9).encode("utf-8"), + blank=six.unichr(0x25CC).encode("utf-8"), + marker=six.unichr(0x25CE).encode("utf-8"), + format='[%(fill)s%(marker)s%(blank)s] %(progress)s%%', + incremental=True, + stdout=sys.stdout): + super(ProgressWithEvents, self).__init__(start,end,width,fill,blank,marker,format,incremental,stdout) + self.events = {} + + def add_event(self,event): + if event in self.events: + self.events[event] += 1 + else: + self.events[event] = 1 + + def show_progress(self): + isatty = hasattr(self.stdout, 'isatty') and self.stdout.isatty() + if isatty: + self.stdout.write('\r') + else: + self.stdout.write('\n') + self.stdout.write(str(self)) + if len(self.events) == 0: + return + self.stdout.write('\n') + for key in list(self.events.keys()): + self.stdout.write(str(key) + ' = ' + str(self.events[key]) + ' ') + if isatty: + self.stdout.write('\033[1A') + self.stdout.flush() + + +if __name__ == '__main__': + p = AnimatedProgressBar(end=200, width=200) + + while True: + p + 5 + p.show_progress() + time.sleep(0.3) + if p.progress == 100: + break + print() #new line
\ No newline at end of file diff --git a/third_party/Python/module/six/LICENSE b/third_party/Python/module/six/LICENSE new file mode 100644 index 000000000000..e558f9d494ab --- /dev/null +++ b/third_party/Python/module/six/LICENSE @@ -0,0 +1,18 @@ +Copyright (c) 2010-2015 Benjamin Peterson + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/third_party/Python/module/six/six.py b/third_party/Python/module/six/six.py new file mode 100644 index 000000000000..190c0239cd7d --- /dev/null +++ b/third_party/Python/module/six/six.py @@ -0,0 +1,868 @@ +"""Utilities for writing code that runs on Python 2 and 3""" + +# Copyright (c) 2010-2015 Benjamin Peterson +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +from __future__ import absolute_import + +import functools +import itertools +import operator +import sys +import types + +__author__ = "Benjamin Peterson <benjamin@python.org>" +__version__ = "1.10.0" + + +# Useful for very coarse version differentiation. +PY2 = sys.version_info[0] == 2 +PY3 = sys.version_info[0] == 3 +PY34 = sys.version_info[0:2] >= (3, 4) + +if PY3: + string_types = str, + integer_types = int, + class_types = type, + text_type = str + binary_type = bytes + + MAXSIZE = sys.maxsize +else: + string_types = basestring, + integer_types = (int, long) + class_types = (type, types.ClassType) + text_type = unicode + binary_type = str + + if sys.platform.startswith("java"): + # Jython always uses 32 bits. + MAXSIZE = int((1 << 31) - 1) + else: + # It's possible to have sizeof(long) != sizeof(Py_ssize_t). + class X(object): + + def __len__(self): + return 1 << 31 + try: + len(X()) + except OverflowError: + # 32-bit + MAXSIZE = int((1 << 31) - 1) + else: + # 64-bit + MAXSIZE = int((1 << 63) - 1) + del X + + +def _add_doc(func, doc): + """Add documentation to a function.""" + func.__doc__ = doc + + +def _import_module(name): + """Import module, returning the module after the last dot.""" + __import__(name) + return sys.modules[name] + + +class _LazyDescr(object): + + def __init__(self, name): + self.name = name + + def __get__(self, obj, tp): + result = self._resolve() + setattr(obj, self.name, result) # Invokes __set__. + try: + # This is a bit ugly, but it avoids running this again by + # removing this descriptor. + delattr(obj.__class__, self.name) + except AttributeError: + pass + return result + + +class MovedModule(_LazyDescr): + + def __init__(self, name, old, new=None): + super(MovedModule, self).__init__(name) + if PY3: + if new is None: + new = name + self.mod = new + else: + self.mod = old + + def _resolve(self): + return _import_module(self.mod) + + def __getattr__(self, attr): + _module = self._resolve() + value = getattr(_module, attr) + setattr(self, attr, value) + return value + + +class _LazyModule(types.ModuleType): + + def __init__(self, name): + super(_LazyModule, self).__init__(name) + self.__doc__ = self.__class__.__doc__ + + def __dir__(self): + attrs = ["__doc__", "__name__"] + attrs += [attr.name for attr in self._moved_attributes] + return attrs + + # Subclasses should override this + _moved_attributes = [] + + +class MovedAttribute(_LazyDescr): + + def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): + super(MovedAttribute, self).__init__(name) + if PY3: + if new_mod is None: + new_mod = name + self.mod = new_mod + if new_attr is None: + if old_attr is None: + new_attr = name + else: + new_attr = old_attr + self.attr = new_attr + else: + self.mod = old_mod + if old_attr is None: + old_attr = name + self.attr = old_attr + + def _resolve(self): + module = _import_module(self.mod) + return getattr(module, self.attr) + + +class _SixMetaPathImporter(object): + + """ + A meta path importer to import six.moves and its submodules. + + This class implements a PEP302 finder and loader. It should be compatible + with Python 2.5 and all existing versions of Python3 + """ + + def __init__(self, six_module_name): + self.name = six_module_name + self.known_modules = {} + + def _add_module(self, mod, *fullnames): + for fullname in fullnames: + self.known_modules[self.name + "." + fullname] = mod + + def _get_module(self, fullname): + return self.known_modules[self.name + "." + fullname] + + def find_module(self, fullname, path=None): + if fullname in self.known_modules: + return self + return None + + def __get_module(self, fullname): + try: + return self.known_modules[fullname] + except KeyError: + raise ImportError("This loader does not know module " + fullname) + + def load_module(self, fullname): + try: + # in case of a reload + return sys.modules[fullname] + except KeyError: + pass + mod = self.__get_module(fullname) + if isinstance(mod, MovedModule): + mod = mod._resolve() + else: + mod.__loader__ = self + sys.modules[fullname] = mod + return mod + + def is_package(self, fullname): + """ + Return true, if the named module is a package. + + We need this method to get correct spec objects with + Python 3.4 (see PEP451) + """ + return hasattr(self.__get_module(fullname), "__path__") + + def get_code(self, fullname): + """Return None + + Required, if is_package is implemented""" + self.__get_module(fullname) # eventually raises ImportError + return None + get_source = get_code # same as get_code + +_importer = _SixMetaPathImporter(__name__) + + +class _MovedItems(_LazyModule): + + """Lazy loading of moved objects""" + __path__ = [] # mark as package + + +_moved_attributes = [ + MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), + MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), + MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), + MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), + MovedAttribute("intern", "__builtin__", "sys"), + MovedAttribute("map", "itertools", "builtins", "imap", "map"), + MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), + MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), + MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), + MovedAttribute("reduce", "__builtin__", "functools"), + MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), + MovedAttribute("StringIO", "StringIO", "io"), + MovedAttribute("UserDict", "UserDict", "collections"), + MovedAttribute("UserList", "UserList", "collections"), + MovedAttribute("UserString", "UserString", "collections"), + MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), + MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), + MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), + MovedModule("builtins", "__builtin__"), + MovedModule("configparser", "ConfigParser"), + MovedModule("copyreg", "copy_reg"), + MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), + MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), + MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), + MovedModule("http_cookies", "Cookie", "http.cookies"), + MovedModule("html_entities", "htmlentitydefs", "html.entities"), + MovedModule("html_parser", "HTMLParser", "html.parser"), + MovedModule("http_client", "httplib", "http.client"), + MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), + MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), + MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), + MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), + MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), + MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), + MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), + MovedModule("cPickle", "cPickle", "pickle"), + MovedModule("queue", "Queue"), + MovedModule("reprlib", "repr"), + MovedModule("socketserver", "SocketServer"), + MovedModule("_thread", "thread", "_thread"), + MovedModule("tkinter", "Tkinter"), + MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), + MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), + MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), + MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), + MovedModule("tkinter_tix", "Tix", "tkinter.tix"), + MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), + MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), + MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), + MovedModule("tkinter_colorchooser", "tkColorChooser", + "tkinter.colorchooser"), + MovedModule("tkinter_commondialog", "tkCommonDialog", + "tkinter.commondialog"), + MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), + MovedModule("tkinter_font", "tkFont", "tkinter.font"), + MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), + MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", + "tkinter.simpledialog"), + MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), + MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), + MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), + MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), + MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), + MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), +] +# Add windows specific modules. +if sys.platform == "win32": + _moved_attributes += [ + MovedModule("winreg", "_winreg"), + ] + +for attr in _moved_attributes: + setattr(_MovedItems, attr.name, attr) + if isinstance(attr, MovedModule): + _importer._add_module(attr, "moves." + attr.name) +del attr + +_MovedItems._moved_attributes = _moved_attributes + +moves = _MovedItems(__name__ + ".moves") +_importer._add_module(moves, "moves") + + +class Module_six_moves_urllib_parse(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_parse""" + + +_urllib_parse_moved_attributes = [ + MovedAttribute("ParseResult", "urlparse", "urllib.parse"), + MovedAttribute("SplitResult", "urlparse", "urllib.parse"), + MovedAttribute("parse_qs", "urlparse", "urllib.parse"), + MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), + MovedAttribute("urldefrag", "urlparse", "urllib.parse"), + MovedAttribute("urljoin", "urlparse", "urllib.parse"), + MovedAttribute("urlparse", "urlparse", "urllib.parse"), + MovedAttribute("urlsplit", "urlparse", "urllib.parse"), + MovedAttribute("urlunparse", "urlparse", "urllib.parse"), + MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), + MovedAttribute("quote", "urllib", "urllib.parse"), + MovedAttribute("quote_plus", "urllib", "urllib.parse"), + MovedAttribute("unquote", "urllib", "urllib.parse"), + MovedAttribute("unquote_plus", "urllib", "urllib.parse"), + MovedAttribute("urlencode", "urllib", "urllib.parse"), + MovedAttribute("splitquery", "urllib", "urllib.parse"), + MovedAttribute("splittag", "urllib", "urllib.parse"), + MovedAttribute("splituser", "urllib", "urllib.parse"), + MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), + MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), + MovedAttribute("uses_params", "urlparse", "urllib.parse"), + MovedAttribute("uses_query", "urlparse", "urllib.parse"), + MovedAttribute("uses_relative", "urlparse", "urllib.parse"), +] +for attr in _urllib_parse_moved_attributes: + setattr(Module_six_moves_urllib_parse, attr.name, attr) +del attr + +Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes + +_importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), + "moves.urllib_parse", "moves.urllib.parse") + + +class Module_six_moves_urllib_error(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_error""" + + +_urllib_error_moved_attributes = [ + MovedAttribute("URLError", "urllib2", "urllib.error"), + MovedAttribute("HTTPError", "urllib2", "urllib.error"), + MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), +] +for attr in _urllib_error_moved_attributes: + setattr(Module_six_moves_urllib_error, attr.name, attr) +del attr + +Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes + +_importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), + "moves.urllib_error", "moves.urllib.error") + + +class Module_six_moves_urllib_request(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_request""" + + +_urllib_request_moved_attributes = [ + MovedAttribute("urlopen", "urllib2", "urllib.request"), + MovedAttribute("install_opener", "urllib2", "urllib.request"), + MovedAttribute("build_opener", "urllib2", "urllib.request"), + MovedAttribute("pathname2url", "urllib", "urllib.request"), + MovedAttribute("url2pathname", "urllib", "urllib.request"), + MovedAttribute("getproxies", "urllib", "urllib.request"), + MovedAttribute("Request", "urllib2", "urllib.request"), + MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), + MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), + MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), + MovedAttribute("BaseHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), + MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), + MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), + MovedAttribute("FileHandler", "urllib2", "urllib.request"), + MovedAttribute("FTPHandler", "urllib2", "urllib.request"), + MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), + MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), + MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), + MovedAttribute("urlretrieve", "urllib", "urllib.request"), + MovedAttribute("urlcleanup", "urllib", "urllib.request"), + MovedAttribute("URLopener", "urllib", "urllib.request"), + MovedAttribute("FancyURLopener", "urllib", "urllib.request"), + MovedAttribute("proxy_bypass", "urllib", "urllib.request"), +] +for attr in _urllib_request_moved_attributes: + setattr(Module_six_moves_urllib_request, attr.name, attr) +del attr + +Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes + +_importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), + "moves.urllib_request", "moves.urllib.request") + + +class Module_six_moves_urllib_response(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_response""" + + +_urllib_response_moved_attributes = [ + MovedAttribute("addbase", "urllib", "urllib.response"), + MovedAttribute("addclosehook", "urllib", "urllib.response"), + MovedAttribute("addinfo", "urllib", "urllib.response"), + MovedAttribute("addinfourl", "urllib", "urllib.response"), +] +for attr in _urllib_response_moved_attributes: + setattr(Module_six_moves_urllib_response, attr.name, attr) +del attr + +Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes + +_importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), + "moves.urllib_response", "moves.urllib.response") + + +class Module_six_moves_urllib_robotparser(_LazyModule): + + """Lazy loading of moved objects in six.moves.urllib_robotparser""" + + +_urllib_robotparser_moved_attributes = [ + MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), +] +for attr in _urllib_robotparser_moved_attributes: + setattr(Module_six_moves_urllib_robotparser, attr.name, attr) +del attr + +Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes + +_importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), + "moves.urllib_robotparser", "moves.urllib.robotparser") + + +class Module_six_moves_urllib(types.ModuleType): + + """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" + __path__ = [] # mark as package + parse = _importer._get_module("moves.urllib_parse") + error = _importer._get_module("moves.urllib_error") + request = _importer._get_module("moves.urllib_request") + response = _importer._get_module("moves.urllib_response") + robotparser = _importer._get_module("moves.urllib_robotparser") + + def __dir__(self): + return ['parse', 'error', 'request', 'response', 'robotparser'] + +_importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), + "moves.urllib") + + +def add_move(move): + """Add an item to six.moves.""" + setattr(_MovedItems, move.name, move) + + +def remove_move(name): + """Remove item from six.moves.""" + try: + delattr(_MovedItems, name) + except AttributeError: + try: + del moves.__dict__[name] + except KeyError: + raise AttributeError("no such move, %r" % (name,)) + + +if PY3: + _meth_func = "__func__" + _meth_self = "__self__" + + _func_closure = "__closure__" + _func_code = "__code__" + _func_defaults = "__defaults__" + _func_globals = "__globals__" +else: + _meth_func = "im_func" + _meth_self = "im_self" + + _func_closure = "func_closure" + _func_code = "func_code" + _func_defaults = "func_defaults" + _func_globals = "func_globals" + + +try: + advance_iterator = next +except NameError: + def advance_iterator(it): + return it.next() +next = advance_iterator + + +try: + callable = callable +except NameError: + def callable(obj): + return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) + + +if PY3: + def get_unbound_function(unbound): + return unbound + + create_bound_method = types.MethodType + + def create_unbound_method(func, cls): + return func + + Iterator = object +else: + def get_unbound_function(unbound): + return unbound.im_func + + def create_bound_method(func, obj): + return types.MethodType(func, obj, obj.__class__) + + def create_unbound_method(func, cls): + return types.MethodType(func, None, cls) + + class Iterator(object): + + def next(self): + return type(self).__next__(self) + + callable = callable +_add_doc(get_unbound_function, + """Get the function out of a possibly unbound function""") + + +get_method_function = operator.attrgetter(_meth_func) +get_method_self = operator.attrgetter(_meth_self) +get_function_closure = operator.attrgetter(_func_closure) +get_function_code = operator.attrgetter(_func_code) +get_function_defaults = operator.attrgetter(_func_defaults) +get_function_globals = operator.attrgetter(_func_globals) + + +if PY3: + def iterkeys(d, **kw): + return iter(d.keys(**kw)) + + def itervalues(d, **kw): + return iter(d.values(**kw)) + + def iteritems(d, **kw): + return iter(d.items(**kw)) + + def iterlists(d, **kw): + return iter(d.lists(**kw)) + + viewkeys = operator.methodcaller("keys") + + viewvalues = operator.methodcaller("values") + + viewitems = operator.methodcaller("items") +else: + def iterkeys(d, **kw): + return d.iterkeys(**kw) + + def itervalues(d, **kw): + return d.itervalues(**kw) + + def iteritems(d, **kw): + return d.iteritems(**kw) + + def iterlists(d, **kw): + return d.iterlists(**kw) + + viewkeys = operator.methodcaller("viewkeys") + + viewvalues = operator.methodcaller("viewvalues") + + viewitems = operator.methodcaller("viewitems") + +_add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") +_add_doc(itervalues, "Return an iterator over the values of a dictionary.") +_add_doc(iteritems, + "Return an iterator over the (key, value) pairs of a dictionary.") +_add_doc(iterlists, + "Return an iterator over the (key, [values]) pairs of a dictionary.") + + +if PY3: + def b(s): + return s.encode("latin-1") + + def u(s): + return s + unichr = chr + import struct + int2byte = struct.Struct(">B").pack + del struct + byte2int = operator.itemgetter(0) + indexbytes = operator.getitem + iterbytes = iter + import io + StringIO = io.StringIO + BytesIO = io.BytesIO + _assertCountEqual = "assertCountEqual" + if sys.version_info[1] <= 1: + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" + else: + _assertRaisesRegex = "assertRaisesRegex" + _assertRegex = "assertRegex" +else: + def b(s): + return s + # Workaround for standalone backslash + + def u(s): + return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") + unichr = unichr + int2byte = chr + + def byte2int(bs): + return ord(bs[0]) + + def indexbytes(buf, i): + return ord(buf[i]) + iterbytes = functools.partial(itertools.imap, ord) + import StringIO + StringIO = BytesIO = StringIO.StringIO + _assertCountEqual = "assertItemsEqual" + _assertRaisesRegex = "assertRaisesRegexp" + _assertRegex = "assertRegexpMatches" +_add_doc(b, """Byte literal""") +_add_doc(u, """Text literal""") + + +def assertCountEqual(self, *args, **kwargs): + return getattr(self, _assertCountEqual)(*args, **kwargs) + + +def assertRaisesRegex(self, *args, **kwargs): + return getattr(self, _assertRaisesRegex)(*args, **kwargs) + + +def assertRegex(self, *args, **kwargs): + return getattr(self, _assertRegex)(*args, **kwargs) + + +if PY3: + exec_ = getattr(moves.builtins, "exec") + + def reraise(tp, value, tb=None): + if value is None: + value = tp() + if value.__traceback__ is not tb: + raise value.with_traceback(tb) + raise value + +else: + def exec_(_code_, _globs_=None, _locs_=None): + """Execute code in a namespace.""" + if _globs_ is None: + frame = sys._getframe(1) + _globs_ = frame.f_globals + if _locs_ is None: + _locs_ = frame.f_locals + del frame + elif _locs_ is None: + _locs_ = _globs_ + exec("""exec _code_ in _globs_, _locs_""") + + exec_("""def reraise(tp, value, tb=None): + raise tp, value, tb +""") + + +if sys.version_info[:2] == (3, 2): + exec_("""def raise_from(value, from_value): + if from_value is None: + raise value + raise value from from_value +""") +elif sys.version_info[:2] > (3, 2): + exec_("""def raise_from(value, from_value): + raise value from from_value +""") +else: + def raise_from(value, from_value): + raise value + + +print_ = getattr(moves.builtins, "print", None) +if print_ is None: + def print_(*args, **kwargs): + """The new-style print function for Python 2.4 and 2.5.""" + fp = kwargs.pop("file", sys.stdout) + if fp is None: + return + + def write(data): + if not isinstance(data, basestring): + data = str(data) + # If the file has an encoding, encode unicode with it. + if (isinstance(fp, file) and + isinstance(data, unicode) and + fp.encoding is not None): + errors = getattr(fp, "errors", None) + if errors is None: + errors = "strict" + data = data.encode(fp.encoding, errors) + fp.write(data) + want_unicode = False + sep = kwargs.pop("sep", None) + if sep is not None: + if isinstance(sep, unicode): + want_unicode = True + elif not isinstance(sep, str): + raise TypeError("sep must be None or a string") + end = kwargs.pop("end", None) + if end is not None: + if isinstance(end, unicode): + want_unicode = True + elif not isinstance(end, str): + raise TypeError("end must be None or a string") + if kwargs: + raise TypeError("invalid keyword arguments to print()") + if not want_unicode: + for arg in args: + if isinstance(arg, unicode): + want_unicode = True + break + if want_unicode: + newline = unicode("\n") + space = unicode(" ") + else: + newline = "\n" + space = " " + if sep is None: + sep = space + if end is None: + end = newline + for i, arg in enumerate(args): + if i: + write(sep) + write(arg) + write(end) +if sys.version_info[:2] < (3, 3): + _print = print_ + + def print_(*args, **kwargs): + fp = kwargs.get("file", sys.stdout) + flush = kwargs.pop("flush", False) + _print(*args, **kwargs) + if flush and fp is not None: + fp.flush() + +_add_doc(reraise, """Reraise an exception.""") + +if sys.version_info[0:2] < (3, 4): + def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, + updated=functools.WRAPPER_UPDATES): + def wrapper(f): + f = functools.wraps(wrapped, assigned, updated)(f) + f.__wrapped__ = wrapped + return f + return wrapper +else: + wraps = functools.wraps + + +def with_metaclass(meta, *bases): + """Create a base class with a metaclass.""" + # This requires a bit of explanation: the basic idea is to make a dummy + # metaclass for one level of class instantiation that replaces itself with + # the actual metaclass. + class metaclass(meta): + + def __new__(cls, name, this_bases, d): + return meta(name, bases, d) + return type.__new__(metaclass, 'temporary_class', (), {}) + + +def add_metaclass(metaclass): + """Class decorator for creating a class with a metaclass.""" + def wrapper(cls): + orig_vars = cls.__dict__.copy() + slots = orig_vars.get('__slots__') + if slots is not None: + if isinstance(slots, str): + slots = [slots] + for slots_var in slots: + orig_vars.pop(slots_var) + orig_vars.pop('__dict__', None) + orig_vars.pop('__weakref__', None) + return metaclass(cls.__name__, cls.__bases__, orig_vars) + return wrapper + + +def python_2_unicode_compatible(klass): + """ + A decorator that defines __unicode__ and __str__ methods under Python 2. + Under Python 3 it does nothing. + + To support Python 2 and 3 with a single code base, define a __str__ method + returning text and apply this decorator to the class. + """ + if PY2: + if '__str__' not in klass.__dict__: + raise ValueError("@python_2_unicode_compatible cannot be applied " + "to %s because it doesn't define __str__()." % + klass.__name__) + klass.__unicode__ = klass.__str__ + klass.__str__ = lambda self: self.__unicode__().encode('utf-8') + return klass + + +# Complete the moves implementation. +# This code is at the end of this module to speed up module loading. +# Turn this module into a package. +__path__ = [] # required for PEP 302 and PEP 451 +__package__ = __name__ # see PEP 366 @ReservedAssignment +if globals().get("__spec__") is not None: + __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable +# Remove other six meta path importers, since they cause problems. This can +# happen if six is removed from sys.modules and then reloaded. (Setuptools does +# this for some reason.) +if sys.meta_path: + for i, importer in enumerate(sys.meta_path): + # Here's some real nastiness: Another "instance" of the six module might + # be floating around. Therefore, we can't use isinstance() to check for + # the six meta path importer, since the other six instance will have + # inserted an importer with different class. + if (type(importer).__name__ == "_SixMetaPathImporter" and + importer.name == __name__): + del sys.meta_path[i] + break + del i, importer +# Finally, add the importer to the meta path import hook. +sys.meta_path.append(_importer) diff --git a/third_party/Python/module/unittest2/unittest2/__init__.py b/third_party/Python/module/unittest2/unittest2/__init__.py new file mode 100644 index 000000000000..8ec6c7a478a6 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/__init__.py @@ -0,0 +1,78 @@ +""" +unittest2 + +unittest2 is a backport of the new features added to the unittest testing +framework in Python 2.7. It is tested to run on Python 2.4 - 2.6. + +To use unittest2 instead of unittest simply replace ``import unittest`` with +``import unittest2``. + + +Copyright (c) 1999-2003 Steve Purcell +Copyright (c) 2003-2010 Python Software Foundation +This module is free software, and you may redistribute it and/or modify +it under the same terms as Python itself, so long as this copyright message +and disclaimer are retained in their original form. + +IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, +SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF +THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, +AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. +""" + +import sys + +if sys.version_info[0] >= 3: + # Python 3 doesn't have the builtin `cmp` function anymore + cmp_ = lambda x, y: (x > y) - (x < y) +else: + cmp_ = cmp + +reversed_cmp_ = lambda x, y: -cmp_(x,y) + +__all__ = ['TestResult', 'TestCase', 'TestSuite', + 'TextTestRunner', 'TestLoader', 'FunctionTestCase', 'main', + 'defaultTestLoader', 'SkipTest', 'skip', 'skipIf', 'skipUnless', + 'expectedFailure', 'TextTestResult', '__version__', 'collector'] + +__version__ = '0.5.1' + +# Expose obsolete functions for backwards compatibility +__all__.extend(['getTestCaseNames', 'makeSuite', 'findTestCases']) + + +from unittest2.collector import collector +from unittest2.result import TestResult +from unittest2.case import ( + TestCase, FunctionTestCase, SkipTest, skip, skipIf, + skipUnless, expectedFailure +) +from unittest2.suite import BaseTestSuite, TestSuite +from unittest2.loader import ( + TestLoader, defaultTestLoader, makeSuite, getTestCaseNames, + findTestCases +) +from unittest2.main import TestProgram, main, main_ +from unittest2.runner import TextTestRunner, TextTestResult + +try: + from unittest2.signals import ( + installHandler, registerResult, removeResult, removeHandler + ) +except ImportError: + # Compatibility with platforms that don't have the signal module + pass +else: + __all__.extend(['installHandler', 'registerResult', 'removeResult', + 'removeHandler']) + +# deprecated +_TextTestResult = TextTestResult + +__unittest = True diff --git a/third_party/Python/module/unittest2/unittest2/__main__.py b/third_party/Python/module/unittest2/unittest2/__main__.py new file mode 100644 index 000000000000..04ed982df0fb --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/__main__.py @@ -0,0 +1,10 @@ +"""Main entry point""" + +import sys +if sys.argv[0].endswith("__main__.py"): + sys.argv[0] = "unittest2" + +__unittest = True + +from unittest2.main import main_ +main_() diff --git a/third_party/Python/module/unittest2/unittest2/case.py b/third_party/Python/module/unittest2/unittest2/case.py new file mode 100644 index 000000000000..de7cb6a71e98 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/case.py @@ -0,0 +1,1119 @@ +"""Test case implementation""" + +import sys +import difflib +import pprint +import re +import unittest +import warnings + +import six + +from unittest2 import result +from unittest2.util import ( + safe_repr, safe_str, strclass, + unorderable_list_difference +) + +from unittest2.compatibility import wraps + +__unittest = True + + +DIFF_OMITTED = ('\nDiff is %s characters long. ' + 'Set self.maxDiff to None to see it.') + +class SkipTest(Exception): + """ + Raise this exception in a test to skip it. + + Usually you can use TestResult.skip() or one of the skipping decorators + instead of raising this directly. + """ + +class _ExpectedFailure(Exception): + """ + Raise this when a test is expected to fail. + + This is an implementation detail. + """ + + def __init__(self, exc_info, bugnumber=None): + # can't use super because Python 2.4 exceptions are old style + Exception.__init__(self) + self.exc_info = exc_info + self.bugnumber = bugnumber + +class _UnexpectedSuccess(Exception): + """ + The test was supposed to fail, but it didn't! + """ + + def __init__(self, exc_info, bugnumber=None): + # can't use super because Python 2.4 exceptions are old style + Exception.__init__(self) + self.exc_info = exc_info + self.bugnumber = bugnumber + +def _id(obj): + return obj + +def skip(reason): + """ + Unconditionally skip a test. + """ + def decorator(test_item): + if not (isinstance(test_item, type) and issubclass(test_item, TestCase)): + @wraps(test_item) + def skip_wrapper(*args, **kwargs): + raise SkipTest(reason) + test_item = skip_wrapper + + test_item.__unittest_skip__ = True + test_item.__unittest_skip_why__ = reason + return test_item + return decorator + +def skipIf(condition, reason): + """ + Skip a test if the condition is true. + """ + if condition: + return skip(reason) + return _id + +def skipUnless(condition, reason): + """ + Skip a test unless the condition is true. + """ + if not condition: + return skip(reason) + return _id + +def expectedFailure(bugnumber=None): + if callable(bugnumber): + @wraps(bugnumber) + def expectedFailure_easy_wrapper(*args, **kwargs): + try: + bugnumber(*args, **kwargs) + except Exception: + raise _ExpectedFailure(sys.exc_info(),None) + raise _UnexpectedSuccess(sys.exc_info(),None) + return expectedFailure_easy_wrapper + else: + def expectedFailure_impl(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + func(*args, **kwargs) + except Exception: + raise _ExpectedFailure(sys.exc_info(),bugnumber) + raise _UnexpectedSuccess(sys.exc_info(),bugnumber) + return wrapper + return expectedFailure_impl + +class _AssertRaisesContext(object): + """A context manager used to implement TestCase.assertRaises* methods.""" + + def __init__(self, expected, test_case, expected_regexp=None): + self.expected = expected + self.failureException = test_case.failureException + self.expected_regexp = expected_regexp + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if exc_type is None: + try: + exc_name = self.expected.__name__ + except AttributeError: + exc_name = str(self.expected) + raise self.failureException( + "%s not raised" % (exc_name,)) + if not issubclass(exc_type, self.expected): + # let unexpected exceptions pass through + return False + self.exception = exc_value # store for later retrieval + if self.expected_regexp is None: + return True + + expected_regexp = self.expected_regexp + if isinstance(expected_regexp, six.string_types): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, str(exc_value))) + return True + + +class _TypeEqualityDict(object): + + def __init__(self, testcase): + self.testcase = testcase + self._store = {} + + def __setitem__(self, key, value): + self._store[key] = value + + def __getitem__(self, key): + value = self._store[key] + if isinstance(value, six.string_types): + return getattr(self.testcase, value) + return value + + def get(self, key, default=None): + if key in self._store: + return self[key] + return default + + +class TestCase(unittest.TestCase): + """A class whose instances are single test cases. + + By default, the test code itself should be placed in a method named + 'runTest'. + + If the fixture may be used for many test cases, create as + many test methods as are needed. When instantiating such a TestCase + subclass, specify in the constructor arguments the name of the test method + that the instance is to execute. + + Test authors should subclass TestCase for their own tests. Construction + and deconstruction of the test's environment ('fixture') can be + implemented by overriding the 'setUp' and 'tearDown' methods respectively. + + If it is necessary to override the __init__ method, the base class + __init__ method must always be called. It is important that subclasses + should not change the signature of their __init__ method, since instances + of the classes are instantiated automatically by parts of the framework + in order to be run. + """ + + # This attribute determines which exception will be raised when + # the instance's assertion methods fail; test methods raising this + # exception will be deemed to have 'failed' rather than 'errored' + + failureException = AssertionError + + # This attribute sets the maximum length of a diff in failure messages + # by assert methods using difflib. It is looked up as an instance attribute + # so can be configured by individual tests if required. + + maxDiff = 80*8 + + # This attribute determines whether long messages (including repr of + # objects used in assert methods) will be printed on failure in *addition* + # to any explicit message passed. + + longMessage = True + + # Attribute used by TestSuite for classSetUp + + _classSetupFailed = False + + def __init__(self, methodName='runTest'): + """Create an instance of the class that will use the named test + method when executed. Raises a ValueError if the instance does + not have a method with the specified name. + """ + self._testMethodName = methodName + self._resultForDoCleanups = None + try: + testMethod = getattr(self, methodName) + except AttributeError: + raise ValueError("no such test method in %s: %s" % \ + (self.__class__, methodName)) + self._testMethodDoc = testMethod.__doc__ + self._cleanups = [] + + # Map types to custom assertEqual functions that will compare + # instances of said type in more detail to generate a more useful + # error message. + self._type_equality_funcs = _TypeEqualityDict(self) + self.addTypeEqualityFunc(dict, 'assertDictEqual') + self.addTypeEqualityFunc(list, 'assertListEqual') + self.addTypeEqualityFunc(tuple, 'assertTupleEqual') + self.addTypeEqualityFunc(set, 'assertSetEqual') + self.addTypeEqualityFunc(frozenset, 'assertSetEqual') + if six.PY2: + self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual') + else: + self.addTypeEqualityFunc(str, 'assertMultiLineEqual') + + def addTypeEqualityFunc(self, typeobj, function): + """Add a type specific assertEqual style function to compare a type. + + This method is for use by TestCase subclasses that need to register + their own type equality functions to provide nicer error messages. + + Args: + typeobj: The data type to call this function on when both values + are of the same type in assertEqual(). + function: The callable taking two arguments and an optional + msg= argument that raises self.failureException with a + useful error message when the two arguments are not equal. + """ + self._type_equality_funcs[typeobj] = function + + def addCleanup(self, function, *args, **kwargs): + """Add a function, with arguments, to be called when the test is + completed. Functions added are called on a LIFO basis and are + called after tearDown on test failure or success. + + Cleanup items are called even if setUp fails (unlike tearDown).""" + self._cleanups.append((function, args, kwargs)) + + def setUp(self): + "Hook method for setting up the test fixture before exercising it." + + @classmethod + def setUpClass(cls): + "Hook method for setting up class fixture before running tests in the class." + + @classmethod + def tearDownClass(cls): + "Hook method for deconstructing the class fixture after running all tests in the class." + + def tearDown(self): + "Hook method for deconstructing the test fixture after testing it." + + def countTestCases(self): + return 1 + + def defaultTestResult(self): + return result.TestResult() + + def shortDescription(self): + """Returns a one-line description of the test, or None if no + description has been provided. + + The default implementation of this method returns the first line of + the specified test method's docstring. + """ + doc = self._testMethodDoc + return doc and doc.split("\n")[0].strip() or None + + + def id(self): + return "%s.%s" % (strclass(self.__class__), self._testMethodName) + + def __eq__(self, other): + if type(self) is not type(other): + return NotImplemented + + return self._testMethodName == other._testMethodName + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((type(self), self._testMethodName)) + + def __str__(self): + return "%s (%s)" % (self._testMethodName, strclass(self.__class__)) + + def __repr__(self): + return "<%s testMethod=%s>" % \ + (strclass(self.__class__), self._testMethodName) + + def _addSkip(self, result, reason): + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None: + addSkip(self, reason) + else: + warnings.warn("Use of a TestResult without an addSkip method is deprecated", + DeprecationWarning, 2) + result.addSuccess(self) + + def run(self, result=None): + orig_result = result + if result is None: + result = self.defaultTestResult() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + + self._resultForDoCleanups = result + result.startTest(self) + + testMethod = getattr(self, self._testMethodName) + + if (getattr(self.__class__, "__unittest_skip__", False) or + getattr(testMethod, "__unittest_skip__", False)): + # If the class or method was skipped. + try: + skip_why = (getattr(self.__class__, '__unittest_skip_why__', '') + or getattr(testMethod, '__unittest_skip_why__', '')) + self._addSkip(result, skip_why) + finally: + result.stopTest(self) + return + try: + success = False + try: + self.setUp() + except SkipTest as e: + self._addSkip(result, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + success = self.runMethod(testMethod, result) + + try: + self.tearDown() + except Exception: + result.addCleanupError(self, sys.exc_info()) + success = False + + self.dumpSessionInfo() + + cleanUpSuccess = self.doCleanups() + success = success and cleanUpSuccess + if success: + result.addSuccess(self) + finally: + result.stopTest(self) + if orig_result is None: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + + def runMethod(self, testMethod, result): + """Runs the test method and catches any exception that might be thrown. + + This is factored out of TestCase.run() to ensure that any exception + thrown during the test goes out of scope before tearDown. Otherwise, an + exception could hold references to Python objects that are bound to + SB objects and prevent them from being deleted in time. + """ + try: + testMethod() + except self.failureException: + result.addFailure(self, sys.exc_info()) + except _ExpectedFailure as e: + addExpectedFailure = getattr(result, 'addExpectedFailure', None) + if addExpectedFailure is not None: + addExpectedFailure(self, e.exc_info, e.bugnumber) + else: + warnings.warn("Use of a TestResult without an addExpectedFailure method is deprecated", + DeprecationWarning) + result.addSuccess(self) + except _UnexpectedSuccess as x: + addUnexpectedSuccess = getattr(result, 'addUnexpectedSuccess', None) + if addUnexpectedSuccess is not None: + addUnexpectedSuccess(self, x.bugnumber) + else: + warnings.warn("Use of a TestResult without an addUnexpectedSuccess method is deprecated", + DeprecationWarning) + result.addFailure(self, sys.exc_info()) + except SkipTest as e: + self._addSkip(result, str(e)) + except Exception: + result.addError(self, sys.exc_info()) + else: + return True + return False + + def doCleanups(self): + """Execute all cleanup functions. Normally called for you after + tearDown.""" + result = self._resultForDoCleanups + ok = True + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + try: + function(*args, **kwargs) + except Exception: + ok = False + result.addError(self, sys.exc_info()) + return ok + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the test without collecting errors in a TestResult""" + self.setUp() + getattr(self, self._testMethodName)() + self.tearDown() + while self._cleanups: + function, args, kwargs = self._cleanups.pop(-1) + function(*args, **kwargs) + + def skipTest(self, reason): + """Skip this test.""" + raise SkipTest(reason) + + def fail(self, msg=None): + """Fail immediately, with the given message.""" + raise self.failureException(msg) + + def assertFalse(self, expr, msg=None): + "Fail the test if the expression is true." + if expr: + msg = self._formatMessage(msg, "%s is not False" % safe_repr(expr)) + raise self.failureException(msg) + + def assertTrue(self, expr, msg=None): + """Fail the test unless the expression is true.""" + if not expr: + msg = self._formatMessage(msg, "%s is not True" % safe_repr(expr)) + raise self.failureException(msg) + + def _formatMessage(self, msg, standardMsg): + """Honour the longMessage attribute when generating failure messages. + If longMessage is False this means: + * Use only an explicit message if it is provided + * Otherwise use the standard message for the assert + + If longMessage is True: + * Use the standard message + * If an explicit message is provided, plus ' : ' and the explicit message + """ + if not self.longMessage: + return msg or standardMsg + if msg is None: + return standardMsg + try: + return '%s : %s' % (standardMsg, msg) + except UnicodeDecodeError: + return '%s : %s' % (safe_str(standardMsg), safe_str(msg)) + + + def assertRaises(self, excClass, callableObj=None, *args, **kwargs): + """Fail unless an exception of class excClass is thrown + by callableObj when invoked with arguments args and keyword + arguments kwargs. If a different type of exception is + thrown, it will not be caught, and the test case will be + deemed to have suffered an error, exactly as for an + unexpected exception. + + If called with callableObj omitted or None, will return a + context object used like this:: + + with self.assertRaises(SomeException): + do_something() + + The context manager keeps a reference to the exception as + the 'exception' attribute. This allows you to inspect the + exception after the assertion:: + + with self.assertRaises(SomeException) as cm: + do_something() + the_exception = cm.exception + self.assertEqual(the_exception.error_code, 3) + """ + if callableObj is None: + return _AssertRaisesContext(excClass, self) + try: + callableObj(*args, **kwargs) + except excClass: + return + + if hasattr(excClass,'__name__'): + excName = excClass.__name__ + else: + excName = str(excClass) + raise self.failureException("%s not raised" % excName) + + def _getAssertEqualityFunc(self, first, second): + """Get a detailed comparison function for the types of the two args. + + Returns: A callable accepting (first, second, msg=None) that will + raise a failure exception if first != second with a useful human + readable error message for those types. + """ + # + # NOTE(gregory.p.smith): I considered isinstance(first, type(second)) + # and vice versa. I opted for the conservative approach in case + # subclasses are not intended to be compared in detail to their super + # class instances using a type equality func. This means testing + # subtypes won't automagically use the detailed comparison. Callers + # should use their type specific assertSpamEqual method to compare + # subclasses if the detailed comparison is desired and appropriate. + # See the discussion in http://bugs.python.org/issue2578. + # + if type(first) is type(second): + asserter = self._type_equality_funcs.get(type(first)) + if asserter is not None: + return asserter + + return self._baseAssertEqual + + def _baseAssertEqual(self, first, second, msg=None): + """The default assertEqual implementation, not type specific.""" + if not first == second: + standardMsg = '%s != %s' % (safe_repr(first), safe_repr(second)) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertEqual(self, first, second, msg=None): + """Fail if the two objects are unequal as determined by the '==' + operator. + """ + assertion_func = self._getAssertEqualityFunc(first, second) + assertion_func(first, second, msg=msg) + + def assertNotEqual(self, first, second, msg=None): + """Fail if the two objects are equal as determined by the '==' + operator. + """ + if not first != second: + msg = self._formatMessage(msg, '%s == %s' % (safe_repr(first), + safe_repr(second))) + raise self.failureException(msg) + + def assertAlmostEqual(self, first, second, places=None, msg=None, delta=None): + """Fail if the two objects are unequal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + between the two objects is more than the given delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + + If the two objects compare equal then they will automatically + compare almost equal. + """ + if first == second: + # shortcut + return + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + + if delta is not None: + if abs(first - second) <= delta: + return + + standardMsg = '%s != %s within %s delta' % (safe_repr(first), + safe_repr(second), + safe_repr(delta)) + else: + if places is None: + places = 7 + + if round(abs(second-first), places) == 0: + return + + standardMsg = '%s != %s within %r places' % (safe_repr(first), + safe_repr(second), + places) + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + def assertNotAlmostEqual(self, first, second, places=None, msg=None, delta=None): + """Fail if the two objects are equal as determined by their + difference rounded to the given number of decimal places + (default 7) and comparing to zero, or by comparing that the + between the two objects is less than the given delta. + + Note that decimal places (from zero) are usually not the same + as significant digits (measured from the most signficant digit). + + Objects that are equal automatically fail. + """ + if delta is not None and places is not None: + raise TypeError("specify delta or places not both") + if delta is not None: + if not (first == second) and abs(first - second) > delta: + return + standardMsg = '%s == %s within %s delta' % (safe_repr(first), + safe_repr(second), + safe_repr(delta)) + else: + if places is None: + places = 7 + if not (first == second) and round(abs(second-first), places) != 0: + return + standardMsg = '%s == %s within %r places' % (safe_repr(first), + safe_repr(second), + places) + + msg = self._formatMessage(msg, standardMsg) + raise self.failureException(msg) + + # Synonyms for assertion methods + + # The plurals are undocumented. Keep them that way to discourage use. + # Do not add more. Do not remove. + # Going through a deprecation cycle on these would annoy many people. + assertEquals = assertEqual + assertNotEquals = assertNotEqual + assertAlmostEquals = assertAlmostEqual + assertNotAlmostEquals = assertNotAlmostEqual + assert_ = assertTrue + + # These fail* assertion method names are pending deprecation and will + # be a DeprecationWarning in 3.2; http://bugs.python.org/issue2578 + def _deprecate(original_func): + def deprecated_func(*args, **kwargs): + warnings.warn( + ('Please use %s instead.' % original_func.__name__), + PendingDeprecationWarning, 2) + return original_func(*args, **kwargs) + return deprecated_func + + failUnlessEqual = _deprecate(assertEqual) + failIfEqual = _deprecate(assertNotEqual) + failUnlessAlmostEqual = _deprecate(assertAlmostEqual) + failIfAlmostEqual = _deprecate(assertNotAlmostEqual) + failUnless = _deprecate(assertTrue) + failUnlessRaises = _deprecate(assertRaises) + failIf = _deprecate(assertFalse) + + def assertSequenceEqual(self, seq1, seq2, + msg=None, seq_type=None, max_diff=80*8): + """An equality assertion for ordered sequences (like lists and tuples). + + For the purposes of this function, a valid ordered sequence type is one + which can be indexed, has a length, and has an equality operator. + + Args: + seq1: The first sequence to compare. + seq2: The second sequence to compare. + seq_type: The expected datatype of the sequences, or None if no + datatype should be enforced. + msg: Optional message to use on failure instead of a list of + differences. + max_diff: Maximum size off the diff, larger diffs are not shown + """ + if seq_type is not None: + seq_type_name = seq_type.__name__ + if not isinstance(seq1, seq_type): + raise self.failureException('First sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq1))) + if not isinstance(seq2, seq_type): + raise self.failureException('Second sequence is not a %s: %s' + % (seq_type_name, safe_repr(seq2))) + else: + seq_type_name = "sequence" + + differing = None + try: + len1 = len(seq1) + except (TypeError, NotImplementedError): + differing = 'First %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + try: + len2 = len(seq2) + except (TypeError, NotImplementedError): + differing = 'Second %s has no length. Non-sequence?' % ( + seq_type_name) + + if differing is None: + if seq1 == seq2: + return + + seq1_repr = repr(seq1) + seq2_repr = repr(seq2) + if len(seq1_repr) > 30: + seq1_repr = seq1_repr[:30] + '...' + if len(seq2_repr) > 30: + seq2_repr = seq2_repr[:30] + '...' + elements = (seq_type_name.capitalize(), seq1_repr, seq2_repr) + differing = '%ss differ: %s != %s\n' % elements + + for i in xrange(min(len1, len2)): + try: + item1 = seq1[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of first %s\n' % + (i, seq_type_name)) + break + + try: + item2 = seq2[i] + except (TypeError, IndexError, NotImplementedError): + differing += ('\nUnable to index element %d of second %s\n' % + (i, seq_type_name)) + break + + if item1 != item2: + differing += ('\nFirst differing element %d:\n%s\n%s\n' % + (i, item1, item2)) + break + else: + if (len1 == len2 and seq_type is None and + type(seq1) != type(seq2)): + # The sequences are the same, but have differing types. + return + + if len1 > len2: + differing += ('\nFirst %s contains %d additional ' + 'elements.\n' % (seq_type_name, len1 - len2)) + try: + differing += ('First extra element %d:\n%s\n' % + (len2, seq1[len2])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of first %s\n' % (len2, seq_type_name)) + elif len1 < len2: + differing += ('\nSecond %s contains %d additional ' + 'elements.\n' % (seq_type_name, len2 - len1)) + try: + differing += ('First extra element %d:\n%s\n' % + (len1, seq2[len1])) + except (TypeError, IndexError, NotImplementedError): + differing += ('Unable to index element %d ' + 'of second %s\n' % (len1, seq_type_name)) + standardMsg = differing + diffMsg = '\n' + '\n'.join( + difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + + standardMsg = self._truncateMessage(standardMsg, diffMsg) + msg = self._formatMessage(msg, standardMsg) + self.fail(msg) + + def _truncateMessage(self, message, diff): + max_diff = self.maxDiff + if max_diff is None or len(diff) <= max_diff: + return message + diff + return message + (DIFF_OMITTED % len(diff)) + + def assertListEqual(self, list1, list2, msg=None): + """A list-specific equality assertion. + + Args: + list1: The first list to compare. + list2: The second list to compare. + msg: Optional message to use on failure instead of a list of + differences. + + """ + self.assertSequenceEqual(list1, list2, msg, seq_type=list) + + def assertTupleEqual(self, tuple1, tuple2, msg=None): + """A tuple-specific equality assertion. + + Args: + tuple1: The first tuple to compare. + tuple2: The second tuple to compare. + msg: Optional message to use on failure instead of a list of + differences. + """ + self.assertSequenceEqual(tuple1, tuple2, msg, seq_type=tuple) + + def assertSetEqual(self, set1, set2, msg=None): + """A set-specific equality assertion. + + Args: + set1: The first set to compare. + set2: The second set to compare. + msg: Optional message to use on failure instead of a list of + differences. + + assertSetEqual uses ducktyping to support + different types of sets, and is optimized for sets specifically + (parameters must support a difference method). + """ + try: + difference1 = set1.difference(set2) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('first argument does not support set difference: %s' % e) + + try: + difference2 = set2.difference(set1) + except TypeError as e: + self.fail('invalid type when attempting set difference: %s' % e) + except AttributeError as e: + self.fail('second argument does not support set difference: %s' % e) + + if not (difference1 or difference2): + return + + lines = [] + if difference1: + lines.append('Items in the first set but not the second:') + for item in difference1: + lines.append(repr(item)) + if difference2: + lines.append('Items in the second set but not the first:') + for item in difference2: + lines.append(repr(item)) + + standardMsg = '\n'.join(lines) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIn(self, member, container, msg=None): + """Just like self.assertTrue(a in b), but with a nicer default message.""" + if member not in container: + standardMsg = '%s not found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIn(self, member, container, msg=None): + """Just like self.assertTrue(a not in b), but with a nicer default message.""" + if member in container: + standardMsg = '%s unexpectedly found in %s' % (safe_repr(member), + safe_repr(container)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIs(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is b), but with a nicer default message.""" + if expr1 is not expr2: + standardMsg = '%s is not %s' % (safe_repr(expr1), safe_repr(expr2)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNot(self, expr1, expr2, msg=None): + """Just like self.assertTrue(a is not b), but with a nicer default message.""" + if expr1 is expr2: + standardMsg = 'unexpectedly identical: %s' % (safe_repr(expr1),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictEqual(self, d1, d2, msg=None): + self.assert_(isinstance(d1, dict), 'First argument is not a dictionary') + self.assert_(isinstance(d2, dict), 'Second argument is not a dictionary') + + if d1 != d2: + standardMsg = '%s != %s' % (safe_repr(d1, True), safe_repr(d2, True)) + diff = ('\n' + '\n'.join(difflib.ndiff( + pprint.pformat(d1).splitlines(), + pprint.pformat(d2).splitlines()))) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertDictContainsSubset(self, expected, actual, msg=None): + """Checks whether actual is a superset of expected.""" + missing = [] + mismatched = [] + for key, value in expected.iteritems(): + if key not in actual: + missing.append(key) + elif value != actual[key]: + mismatched.append('%s, expected: %s, actual: %s' % + (safe_repr(key), safe_repr(value), + safe_repr(actual[key]))) + + if not (missing or mismatched): + return + + standardMsg = '' + if missing: + standardMsg = 'Missing: %s' % ','.join(safe_repr(m) for m in + missing) + if mismatched: + if standardMsg: + standardMsg += '; ' + standardMsg += 'Mismatched values: %s' % ','.join(mismatched) + + self.fail(self._formatMessage(msg, standardMsg)) + + def assertItemsEqual(self, expected_seq, actual_seq, msg=None): + """An unordered sequence specific comparison. It asserts that + expected_seq and actual_seq contain the same elements. It is + the equivalent of:: + + self.assertEqual(sorted(expected_seq), sorted(actual_seq)) + + Raises with an error message listing which elements of expected_seq + are missing from actual_seq and vice versa if any. + + Asserts that each element has the same count in both sequences. + Example: + - [0, 1, 1] and [1, 0, 1] compare equal. + - [0, 0, 1] and [0, 1] compare unequal. + """ + try: + expected = sorted(expected_seq) + actual = sorted(actual_seq) + except TypeError: + # Unsortable items (example: set(), complex(), ...) + expected = list(expected_seq) + actual = list(actual_seq) + missing, unexpected = unorderable_list_difference( + expected, actual, ignore_duplicate=False + ) + else: + return self.assertSequenceEqual(expected, actual, msg=msg) + + errors = [] + if missing: + errors.append('Expected, but missing:\n %s' % + safe_repr(missing)) + if unexpected: + errors.append('Unexpected, but present:\n %s' % + safe_repr(unexpected)) + if errors: + standardMsg = '\n'.join(errors) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertMultiLineEqual(self, first, second, msg=None): + """Assert that two multi-line strings are equal.""" + self.assert_(isinstance(first, six.string_types), ( + 'First argument is not a string')) + self.assert_(isinstance(second, six.string_types), ( + 'Second argument is not a string')) + + if first != second: + standardMsg = '%s != %s' % (safe_repr(first, True), safe_repr(second, True)) + diff = '\n' + ''.join(difflib.ndiff(first.splitlines(True), + second.splitlines(True))) + standardMsg = self._truncateMessage(standardMsg, diff) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLess(self, a, b, msg=None): + """Just like self.assertTrue(a < b), but with a nicer default message.""" + if not a < b: + standardMsg = '%s not less than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertLessEqual(self, a, b, msg=None): + """Just like self.assertTrue(a <= b), but with a nicer default message.""" + if not a <= b: + standardMsg = '%s not less than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreater(self, a, b, msg=None): + """Just like self.assertTrue(a > b), but with a nicer default message.""" + if not a > b: + standardMsg = '%s not greater than %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertGreaterEqual(self, a, b, msg=None): + """Just like self.assertTrue(a >= b), but with a nicer default message.""" + if not a >= b: + standardMsg = '%s not greater than or equal to %s' % (safe_repr(a), safe_repr(b)) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNone(self, obj, msg=None): + """Same as self.assertTrue(obj is None), with a nicer default message.""" + if obj is not None: + standardMsg = '%s is not None' % (safe_repr(obj),) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsNotNone(self, obj, msg=None): + """Included for symmetry with assertIsNone.""" + if obj is None: + standardMsg = 'unexpectedly None' + self.fail(self._formatMessage(msg, standardMsg)) + + def assertIsInstance(self, obj, cls, msg=None): + """Same as self.assertTrue(isinstance(obj, cls)), with a nicer + default message.""" + if not isinstance(obj, cls): + standardMsg = '%s is not an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertNotIsInstance(self, obj, cls, msg=None): + """Included for symmetry with assertIsInstance.""" + if isinstance(obj, cls): + standardMsg = '%s is an instance of %r' % (safe_repr(obj), cls) + self.fail(self._formatMessage(msg, standardMsg)) + + def assertRaisesRegexp(self, expected_exception, expected_regexp, + callable_obj=None, *args, **kwargs): + """Asserts that the message in a raised exception matches a regexp. + + Args: + expected_exception: Exception class expected to be raised. + expected_regexp: Regexp (re pattern object or string) expected + to be found in error message. + callable_obj: Function to be called. + args: Extra args. + kwargs: Extra kwargs. + """ + if callable_obj is None: + return _AssertRaisesContext(expected_exception, self, expected_regexp) + try: + callable_obj(*args, **kwargs) + except expected_exception as exc_value: + if isinstance(expected_regexp, six.string_types): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(str(exc_value)): + raise self.failureException('"%s" does not match "%s"' % + (expected_regexp.pattern, str(exc_value))) + else: + if hasattr(expected_exception, '__name__'): + excName = expected_exception.__name__ + else: + excName = str(expected_exception) + raise self.failureException("%s not raised" % excName) + + + def assertRegexpMatches(self, text, expected_regexp, msg=None): + """Fail the test unless the text matches the regular expression.""" + if isinstance(expected_regexp, six.string_types): + expected_regexp = re.compile(expected_regexp) + if not expected_regexp.search(text): + msg = msg or "Regexp didn't match" + msg = '%s: %r not found in %r' % (msg, expected_regexp.pattern, text) + raise self.failureException(msg) + + def assertNotRegexpMatches(self, text, unexpected_regexp, msg=None): + """Fail the test if the text matches the regular expression.""" + if isinstance(unexpected_regexp, six.string_types): + unexpected_regexp = re.compile(unexpected_regexp) + match = unexpected_regexp.search(text) + if match: + msg = msg or "Regexp matched" + msg = '%s: %r matches %r in %r' % (msg, + text[match.start():match.end()], + unexpected_regexp.pattern, + text) + raise self.failureException(msg) + +class FunctionTestCase(TestCase): + """A test case that wraps a test function. + + This is useful for slipping pre-existing test functions into the + unittest framework. Optionally, set-up and tidy-up functions can be + supplied. As with TestCase, the tidy-up ('tearDown') function will + always be called if the set-up ('setUp') function ran successfully. + """ + + def __init__(self, testFunc, setUp=None, tearDown=None, description=None): + super(FunctionTestCase, self).__init__() + self._setUpFunc = setUp + self._tearDownFunc = tearDown + self._testFunc = testFunc + self._description = description + + def setUp(self): + if self._setUpFunc is not None: + self._setUpFunc() + + def tearDown(self): + if self._tearDownFunc is not None: + self._tearDownFunc() + + def runTest(self): + self._testFunc() + + def id(self): + return self._testFunc.__name__ + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + + return self._setUpFunc == other._setUpFunc and \ + self._tearDownFunc == other._tearDownFunc and \ + self._testFunc == other._testFunc and \ + self._description == other._description + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((type(self), self._setUpFunc, self._tearDownFunc, + self._testFunc, self._description)) + + def __str__(self): + return "%s (%s)" % (strclass(self.__class__), + self._testFunc.__name__) + + def __repr__(self): + return "<%s testFunc=%s>" % (strclass(self.__class__), + self._testFunc) + + def shortDescription(self): + if self._description is not None: + return self._description + doc = self._testFunc.__doc__ + return doc and doc.split("\n")[0].strip() or None diff --git a/third_party/Python/module/unittest2/unittest2/collector.py b/third_party/Python/module/unittest2/unittest2/collector.py new file mode 100644 index 000000000000..28ff3f89ce7d --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/collector.py @@ -0,0 +1,9 @@ +import os +import sys +from unittest2.loader import defaultTestLoader + +def collector(): + # import __main__ triggers code re-execution + __main__ = sys.modules['__main__'] + setupDir = os.path.abspath(os.path.dirname(__main__.__file__)) + return defaultTestLoader.discover(setupDir) diff --git a/third_party/Python/module/unittest2/unittest2/compatibility.py b/third_party/Python/module/unittest2/unittest2/compatibility.py new file mode 100644 index 000000000000..b8f15dd14288 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/compatibility.py @@ -0,0 +1,64 @@ +import os +import sys + +try: + from functools import wraps +except ImportError: + # only needed for Python 2.4 + def wraps(_): + def _wraps(func): + return func + return _wraps + +__unittest = True + +def _relpath_nt(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + if start_list[0].lower() != path_list[0].lower(): + unc_path, rest = os.path.splitunc(path) + unc_start, rest = os.path.splitunc(start) + if bool(unc_path) ^ bool(unc_start): + raise ValueError("Cannot mix UNC and non-UNC paths (%s and %s)" + % (path, start)) + else: + raise ValueError("path is on drive %s, start on drive %s" + % (path_list[0], start_list[0])) + # Work out how much of the filepath is shared by start and path. + for i in range(min(len(start_list), len(path_list))): + if start_list[i].lower() != path_list[i].lower(): + break + else: + i += 1 + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + +# default to posixpath definition +def _relpath_posix(path, start=os.path.curdir): + """Return a relative version of a path""" + + if not path: + raise ValueError("no path specified") + + start_list = os.path.abspath(start).split(os.path.sep) + path_list = os.path.abspath(path).split(os.path.sep) + + # Work out how much of the filepath is shared by start and path. + i = len(os.path.commonprefix([start_list, path_list])) + + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:] + if not rel_list: + return os.path.curdir + return os.path.join(*rel_list) + +if os.path is sys.modules.get('ntpath'): + relpath = _relpath_nt +else: + relpath = _relpath_posix diff --git a/third_party/Python/module/unittest2/unittest2/loader.py b/third_party/Python/module/unittest2/unittest2/loader.py new file mode 100644 index 000000000000..fe86db1e475e --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/loader.py @@ -0,0 +1,314 @@ +"""Loading unittests.""" + +import functools +import os +import re +import sys +import traceback +import types +import unittest + +from fnmatch import fnmatch + +from unittest2 import case, suite, cmp_ + +try: + from os.path import relpath +except ImportError: + from unittest2.compatibility import relpath + +__unittest = True + +# what about .pyc or .pyo (etc) +# we would need to avoid loading the same tests multiple times +# from '.py', '.pyc' *and* '.pyo' +VALID_MODULE_NAME = re.compile(r'[_a-z]\w*\.py$', re.IGNORECASE) + + +def _make_failed_import_test(name, suiteClass): + message = 'Failed to import test module: %s' % name + if hasattr(traceback, 'format_exc'): + # Python 2.3 compatibility + # format_exc returns two frames of discover.py as well + message += '\n%s' % traceback.format_exc() + return _make_failed_test('ModuleImportFailure', name, ImportError(message), + suiteClass) + +def _make_failed_load_tests(name, exception, suiteClass): + return _make_failed_test('LoadTestsFailure', name, exception, suiteClass) + +def _make_failed_test(classname, methodname, exception, suiteClass): + def testFailure(self): + raise exception + attrs = {methodname: testFailure} + TestClass = type(classname, (case.TestCase,), attrs) + return suiteClass((TestClass(methodname),)) + + +class TestLoader(unittest.TestLoader): + """ + This class is responsible for loading tests according to various criteria + and returning them wrapped in a TestSuite + """ + + def __init__(self): + self.testMethodPrefix = 'test' + self.sortTestMethodsUsing = cmp_ + self.suiteClass = suite.TestSuite + self._top_level_dir = None + + def loadTestsFromTestCase(self, testCaseClass): + """Return a suite of all tests cases contained in testCaseClass""" + if issubclass(testCaseClass, suite.TestSuite): + raise TypeError("Test cases should not be derived from TestSuite." + " Maybe you meant to derive from TestCase?") + testCaseNames = self.getTestCaseNames(testCaseClass) + if not testCaseNames and hasattr(testCaseClass, 'runTest'): + testCaseNames = ['runTest'] + loaded_suite = self.suiteClass(map(testCaseClass, testCaseNames)) + return loaded_suite + + def loadTestsFromModule(self, module, use_load_tests=True): + """Return a suite of all tests cases contained in the given module""" + tests = [] + for name in dir(module): + obj = getattr(module, name) + if isinstance(obj, type) and issubclass(obj, unittest.TestCase): + tests.append(self.loadTestsFromTestCase(obj)) + + load_tests = getattr(module, 'load_tests', None) + tests = self.suiteClass(tests) + if use_load_tests and load_tests is not None: + try: + return load_tests(self, tests, None) + except Exception as e: + return _make_failed_load_tests(module.__name__, e, + self.suiteClass) + return tests + + def loadTestsFromName(self, name, module=None): + """Return a suite of all tests cases given a string specifier. + + The name may resolve either to a module, a test case class, a + test method within a test case class, or a callable object which + returns a TestCase or TestSuite instance. + + The method optionally resolves the names relative to a given module. + """ + parts = name.split('.') + if module is None: + parts_copy = parts[:] + while parts_copy: + try: + module = __import__('.'.join(parts_copy)) + break + except ImportError: + del parts_copy[-1] + if not parts_copy: + raise + parts = parts[1:] + obj = module + for part in parts: + parent, obj = obj, getattr(obj, part) + + if isinstance(obj, types.ModuleType): + return self.loadTestsFromModule(obj) + elif isinstance(obj, type) and issubclass(obj, unittest.TestCase): + return self.loadTestsFromTestCase(obj) + elif (isinstance(obj, types.UnboundMethodType) and + isinstance(parent, type) and + issubclass(parent, case.TestCase)): + return self.suiteClass([parent(obj.__name__)]) + elif isinstance(obj, unittest.TestSuite): + return obj + elif hasattr(obj, '__call__'): + test = obj() + if isinstance(test, unittest.TestSuite): + return test + elif isinstance(test, unittest.TestCase): + return self.suiteClass([test]) + else: + raise TypeError("calling %s returned %s, not a test" % + (obj, test)) + else: + raise TypeError("don't know how to make test from: %s" % obj) + + def loadTestsFromNames(self, names, module=None): + """Return a suite of all tests cases found using the given sequence + of string specifiers. See 'loadTestsFromName()'. + """ + suites = [self.loadTestsFromName(name, module) for name in names] + return self.suiteClass(suites) + + def getTestCaseNames(self, testCaseClass): + """Return a sorted sequence of method names found within testCaseClass + """ + def isTestMethod(attrname, testCaseClass=testCaseClass, + prefix=self.testMethodPrefix): + return attrname.startswith(prefix) and \ + hasattr(getattr(testCaseClass, attrname), '__call__') + testFnNames = list(filter(isTestMethod, dir(testCaseClass))) + if self.sortTestMethodsUsing: + testFnNames.sort(key=functools.cmp_to_key(self.sortTestMethodsUsing)) + return testFnNames + + def discover(self, start_dir, pattern='test*.py', top_level_dir=None): + """Find and return all test modules from the specified start + directory, recursing into subdirectories to find them. Only test files + that match the pattern will be loaded. (Using shell style pattern + matching.) + + All test modules must be importable from the top level of the project. + If the start directory is not the top level directory then the top + level directory must be specified separately. + + If a test package name (directory with '__init__.py') matches the + pattern then the package will be checked for a 'load_tests' function. If + this exists then it will be called with loader, tests, pattern. + + If load_tests exists then discovery does *not* recurse into the package, + load_tests is responsible for loading all tests in the package. + + The pattern is deliberately not stored as a loader attribute so that + packages can continue discovery themselves. top_level_dir is stored so + load_tests does not need to pass this argument in to loader.discover(). + """ + set_implicit_top = False + if top_level_dir is None and self._top_level_dir is not None: + # make top_level_dir optional if called from load_tests in a package + top_level_dir = self._top_level_dir + elif top_level_dir is None: + set_implicit_top = True + top_level_dir = start_dir + + top_level_dir = os.path.abspath(top_level_dir) + + if not top_level_dir in sys.path: + # all test modules must be importable from the top level directory + # should we *unconditionally* put the start directory in first + # in sys.path to minimise likelihood of conflicts between installed + # modules and development versions? + sys.path.insert(0, top_level_dir) + self._top_level_dir = top_level_dir + + is_not_importable = False + if os.path.isdir(os.path.abspath(start_dir)): + start_dir = os.path.abspath(start_dir) + if start_dir != top_level_dir: + is_not_importable = not os.path.isfile(os.path.join(start_dir, '__init__.py')) + else: + # support for discovery from dotted module names + try: + __import__(start_dir) + except ImportError: + is_not_importable = True + else: + the_module = sys.modules[start_dir] + top_part = start_dir.split('.')[0] + start_dir = os.path.abspath(os.path.dirname((the_module.__file__))) + if set_implicit_top: + self._top_level_dir = os.path.abspath(os.path.dirname(os.path.dirname(sys.modules[top_part].__file__))) + sys.path.remove(top_level_dir) + + if is_not_importable: + raise ImportError('Start directory is not importable: %r' % start_dir) + + tests = list(self._find_tests(start_dir, pattern)) + return self.suiteClass(tests) + + def _get_name_from_path(self, path): + path = os.path.splitext(os.path.normpath(path))[0] + + _relpath = relpath(path, self._top_level_dir) + assert not os.path.isabs(_relpath), "Path must be within the project" + assert not _relpath.startswith('..'), "Path must be within the project" + + name = _relpath.replace(os.path.sep, '.') + return name + + def _get_module_from_name(self, name): + __import__(name) + return sys.modules[name] + + def _match_path(self, path, full_path, pattern): + # override this method to use alternative matching strategy + return fnmatch(path, pattern) + + def _find_tests(self, start_dir, pattern): + """Used by discovery. Yields test suites it loads.""" + paths = os.listdir(start_dir) + + for path in paths: + full_path = os.path.join(start_dir, path) + if os.path.isfile(full_path): + if not VALID_MODULE_NAME.match(path): + # valid Python identifiers only + continue + if not self._match_path(path, full_path, pattern): + continue + # if the test file matches, load it + name = self._get_name_from_path(full_path) + try: + module = self._get_module_from_name(name) + except: + yield _make_failed_import_test(name, self.suiteClass) + else: + mod_file = os.path.abspath(getattr(module, '__file__', full_path)) + realpath = os.path.splitext(mod_file)[0] + fullpath_noext = os.path.splitext(full_path)[0] + if realpath.lower() != fullpath_noext.lower(): + module_dir = os.path.dirname(realpath) + mod_name = os.path.splitext(os.path.basename(full_path))[0] + expected_dir = os.path.dirname(full_path) + msg = ("%r module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?") + raise ImportError(msg % (mod_name, module_dir, expected_dir)) + yield self.loadTestsFromModule(module) + elif os.path.isdir(full_path): + if not os.path.isfile(os.path.join(full_path, '__init__.py')): + continue + + load_tests = None + tests = None + if fnmatch(path, pattern): + # only check load_tests if the package directory itself matches the filter + name = self._get_name_from_path(full_path) + package = self._get_module_from_name(name) + load_tests = getattr(package, 'load_tests', None) + tests = self.loadTestsFromModule(package, use_load_tests=False) + + if load_tests is None: + if tests is not None: + # tests loaded from package file + yield tests + # recurse into the package + for test in self._find_tests(full_path, pattern): + yield test + else: + try: + yield load_tests(self, tests, pattern) + except Exception as e: + yield _make_failed_load_tests(package.__name__, e, + self.suiteClass) + +defaultTestLoader = TestLoader() + + +def _makeLoader(prefix, sortUsing, suiteClass=None): + loader = TestLoader() + loader.sortTestMethodsUsing = sortUsing + loader.testMethodPrefix = prefix + if suiteClass: + loader.suiteClass = suiteClass + return loader + +def getTestCaseNames(testCaseClass, prefix, sortUsing=cmp_): + return _makeLoader(prefix, sortUsing).getTestCaseNames(testCaseClass) + +def makeSuite(testCaseClass, prefix='test', sortUsing=cmp_, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromTestCase(testCaseClass) + +def findTestCases(module, prefix='test', sortUsing=cmp_, + suiteClass=suite.TestSuite): + return _makeLoader(prefix, sortUsing, suiteClass).loadTestsFromModule(module) diff --git a/third_party/Python/module/unittest2/unittest2/main.py b/third_party/Python/module/unittest2/unittest2/main.py new file mode 100644 index 000000000000..d74b01f9ac23 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/main.py @@ -0,0 +1,242 @@ +"""Unittest main program""" + +import sys +import os +import types +import six + +from unittest2 import loader, runner +try: + from unittest2.signals import installHandler +except ImportError: + installHandler = None + +__unittest = True + +FAILFAST = " -f, --failfast Stop on first failure\n" +CATCHBREAK = " -c, --catch Catch control-C and display results\n" +BUFFEROUTPUT = " -b, --buffer Buffer stdout and stderr during test runs\n" + +USAGE_AS_MAIN = """\ +Usage: %(progName)s [options] [tests] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output +%(failfast)s%(catchbreak)s%(buffer)s +Examples: + %(progName)s test_module - run tests from test_module + %(progName)s test_module.TestClass - run tests from + test_module.TestClass + %(progName)s test_module.TestClass.test_method - run specified test method + +[tests] can be a list of any number of test modules, classes and test +methods. + +Alternative Usage: %(progName)s discover [options] + +Options: + -v, --verbose Verbose output +%(failfast)s%(catchbreak)s%(buffer)s -s directory Directory to start discovery ('.' default) + -p pattern Pattern to match test files ('test*.py' default) + -t directory Top level directory of project (default to + start directory) + +For test discovery all test modules must be importable from the top +level directory of the project. +""" + +USAGE_FROM_MODULE = """\ +Usage: %(progName)s [options] [test] [...] + +Options: + -h, --help Show this message + -v, --verbose Verbose output + -q, --quiet Minimal output +%(failfast)s%(catchbreak)s%(buffer)s +Examples: + %(progName)s - run default set of tests + %(progName)s MyTestSuite - run suite 'MyTestSuite' + %(progName)s MyTestCase.testSomething - run MyTestCase.testSomething + %(progName)s MyTestCase - run all 'test*' test methods + in MyTestCase +""" + + +class TestProgram(object): + """A command-line program that runs a set of tests; this is primarily + for making test modules conveniently executable. + """ + USAGE = USAGE_FROM_MODULE + + # defaults for testing + failfast = catchbreak = buffer = progName = None + + def __init__(self, module='__main__', defaultTest=None, + argv=None, testRunner=None, + testLoader=loader.defaultTestLoader, exit=True, + verbosity=1, failfast=None, catchbreak=None, buffer=None): + if isinstance(module, six.string_types): + self.module = __import__(module) + for part in module.split('.')[1:]: + self.module = getattr(self.module, part) + else: + self.module = module + if argv is None: + argv = sys.argv + + self.exit = exit + self.verbosity = verbosity + self.failfast = failfast + self.catchbreak = catchbreak + self.buffer = buffer + self.defaultTest = defaultTest + self.testRunner = testRunner + self.testLoader = testLoader + self.progName = os.path.basename(argv[0]) + self.parseArgs(argv) + self.runTests() + + def usageExit(self, msg=None): + if msg: + print(msg) + usage = {'progName': self.progName, 'catchbreak': '', 'failfast': '', + 'buffer': ''} + if self.failfast != False: + usage['failfast'] = FAILFAST + if self.catchbreak != False and installHandler is not None: + usage['catchbreak'] = CATCHBREAK + if self.buffer != False: + usage['buffer'] = BUFFEROUTPUT + print(self.USAGE % usage) + sys.exit(2) + + def parseArgs(self, argv): + if len(argv) > 1 and argv[1].lower() == 'discover': + self._do_discovery(argv[2:]) + return + + import getopt + long_opts = ['help', 'verbose', 'quiet', 'failfast', 'catch', 'buffer'] + try: + options, args = getopt.getopt(argv[1:], 'hHvqfcb', long_opts) + for opt, value in options: + if opt in ('-h','-H','--help'): + self.usageExit() + if opt in ('-q','--quiet'): + self.verbosity = 0 + if opt in ('-v','--verbose'): + self.verbosity = 2 + if opt in ('-f','--failfast'): + if self.failfast is None: + self.failfast = True + # Should this raise an exception if -f is not valid? + if opt in ('-c','--catch'): + if self.catchbreak is None and installHandler is not None: + self.catchbreak = True + # Should this raise an exception if -c is not valid? + if opt in ('-b','--buffer'): + if self.buffer is None: + self.buffer = True + # Should this raise an exception if -b is not valid? + if len(args) == 0 and self.defaultTest is None: + # createTests will load tests from self.module + self.testNames = None + elif len(args) > 0: + self.testNames = args + if __name__ == '__main__': + # to support python -m unittest ... + self.module = None + else: + self.testNames = (self.defaultTest,) + self.createTests() + except getopt.error as msg: + self.usageExit(msg) + + def createTests(self): + if self.testNames is None: + self.test = self.testLoader.loadTestsFromModule(self.module) + else: + self.test = self.testLoader.loadTestsFromNames(self.testNames, + self.module) + + def _do_discovery(self, argv, Loader=loader.TestLoader): + # handle command line args for test discovery + self.progName = '%s discover' % self.progName + import optparse + parser = optparse.OptionParser() + parser.prog = self.progName + parser.add_option('-v', '--verbose', dest='verbose', default=False, + help='Verbose output', action='store_true') + if self.failfast != False: + parser.add_option('-f', '--failfast', dest='failfast', default=False, + help='Stop on first fail or error', + action='store_true') + if self.catchbreak != False and installHandler is not None: + parser.add_option('-c', '--catch', dest='catchbreak', default=False, + help='Catch ctrl-C and display results so far', + action='store_true') + if self.buffer != False: + parser.add_option('-b', '--buffer', dest='buffer', default=False, + help='Buffer stdout and stderr during tests', + action='store_true') + parser.add_option('-s', '--start-directory', dest='start', default='.', + help="Directory to start discovery ('.' default)") + parser.add_option('-p', '--pattern', dest='pattern', default='test*.py', + help="Pattern to match tests ('test*.py' default)") + parser.add_option('-t', '--top-level-directory', dest='top', default=None, + help='Top level directory of project (defaults to start directory)') + + options, args = parser.parse_args(argv) + if len(args) > 3: + self.usageExit() + + for name, value in zip(('start', 'pattern', 'top'), args): + setattr(options, name, value) + + # only set options from the parsing here + # if they weren't set explicitly in the constructor + if self.failfast is None: + self.failfast = options.failfast + if self.catchbreak is None and installHandler is not None: + self.catchbreak = options.catchbreak + if self.buffer is None: + self.buffer = options.buffer + + if options.verbose: + self.verbosity = 2 + + start_dir = options.start + pattern = options.pattern + top_level_dir = options.top + + loader = Loader() + self.test = loader.discover(start_dir, pattern, top_level_dir) + + def runTests(self): + if self.catchbreak: + installHandler() + if self.testRunner is None: + self.testRunner = runner.TextTestRunner + if isinstance(self.testRunner, (type, types.ClassType)): + try: + testRunner = self.testRunner(verbosity=self.verbosity, + failfast=self.failfast, + buffer=self.buffer) + except TypeError: + # didn't accept the verbosity, buffer or failfast arguments + testRunner = self.testRunner() + else: + # it is assumed to be a TestRunner instance + testRunner = self.testRunner + self.result = testRunner.run(self.test) + if self.exit: + sys.exit(not self.result.wasSuccessful()) + +main = TestProgram + +def main_(): + TestProgram.USAGE = USAGE_AS_MAIN + main(module=None) + diff --git a/third_party/Python/module/unittest2/unittest2/result.py b/third_party/Python/module/unittest2/unittest2/result.py new file mode 100644 index 000000000000..2e4994d1d825 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/result.py @@ -0,0 +1,195 @@ +"""Test result object""" + +import use_lldb_suite + +import sys +import traceback +import unittest + +from six import StringIO as SixStringIO + +from unittest2 import util +from unittest2.compatibility import wraps + +__unittest = True + +def failfast(method): + @wraps(method) + def inner(self, *args, **kw): + if getattr(self, 'failfast', False): + self.stop() + return method(self, *args, **kw) + return inner + + +STDOUT_LINE = '\nStdout:\n%s' +STDERR_LINE = '\nStderr:\n%s' + +class TestResult(unittest.TestResult): + """Holder for test result information. + + Test results are automatically managed by the TestCase and TestSuite + classes, and do not need to be explicitly manipulated by writers of tests. + + Each instance holds the total number of tests run, and collections of + failures and errors that occurred among those test runs. The collections + contain tuples of (testcase, exceptioninfo), where exceptioninfo is the + formatted traceback of the error that occurred. + """ + _previousTestClass = None + _moduleSetUpFailed = False + + def __init__(self): + self.failfast = False + self.failures = [] + self.passes = [] + self.errors = [] + self.cleanup_errors = [] + self.testsRun = 0 + self.skipped = [] + self.expectedFailures = [] + self.unexpectedSuccesses = [] + self.shouldStop = False + self.buffer = False + self._stdout_buffer = None + self._stderr_buffer = None + self._original_stdout = sys.stdout + self._original_stderr = sys.stderr + self._mirrorOutput = False + + def startTest(self, test): + "Called when the given test is about to be run" + self.testsRun += 1 + self._mirrorOutput = False + if self.buffer: + if self._stderr_buffer is None: + self._stderr_buffer = SixStringIO() + self._stdout_buffer = SixStringIO() + sys.stdout = self._stdout_buffer + sys.stderr = self._stderr_buffer + + def startTestRun(self): + """Called once before any tests are executed. + + See startTest for a method called before each test. + """ + + def stopTest(self, test): + """Called when the given test has been run""" + if self.buffer: + if self._mirrorOutput: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + self._original_stdout.write(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + self._original_stderr.write(STDERR_LINE % error) + + sys.stdout = self._original_stdout + sys.stderr = self._original_stderr + self._stdout_buffer.seek(0) + self._stdout_buffer.truncate() + self._stderr_buffer.seek(0) + self._stderr_buffer.truncate() + self._mirrorOutput = False + + + def stopTestRun(self): + """Called once after all tests are executed. + + See stopTest for a method called after each test. + """ + + @failfast + def addError(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info(). + """ + self.errors.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + def addCleanupError(self, test, err): + """Called when an error has occurred during cleanup. 'err' is a tuple of + values as returned by sys.exc_info(). + """ + self.cleanup_errors.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + @failfast + def addFailure(self, test, err): + """Called when an error has occurred. 'err' is a tuple of values as + returned by sys.exc_info().""" + self.failures.append((test, self._exc_info_to_string(err, test))) + self._mirrorOutput = True + + def addSuccess(self, test): + "Called when a test has completed successfully" + self.passes.append(test) + pass + + def addSkip(self, test, reason): + """Called when a test is skipped.""" + self.skipped.append((test, reason)) + + def addExpectedFailure(self, test, err, bugnumber): + """Called when an expected failure/error occured.""" + self.expectedFailures.append( + (test, self._exc_info_to_string(err, test))) + + @failfast + def addUnexpectedSuccess(self, test, bugnumber): + """Called when a test was expected to fail, but succeed.""" + self.unexpectedSuccesses.append(test) + + def wasSuccessful(self): + "Tells whether or not this result was a success" + return (len(self.failures) + len(self.errors) == 0) + + def stop(self): + "Indicates that the tests should be aborted" + self.shouldStop = True + + def _exc_info_to_string(self, err, test): + """Converts a sys.exc_info()-style tuple of values into a string.""" + exctype, value, tb = err + # Skip test runner traceback levels + while tb and self._is_relevant_tb_level(tb): + tb = tb.tb_next + if exctype is test.failureException: + # Skip assert*() traceback levels + length = self._count_relevant_tb_levels(tb) + msgLines = traceback.format_exception(exctype, value, tb, length) + else: + msgLines = traceback.format_exception(exctype, value, tb) + + if self.buffer: + output = sys.stdout.getvalue() + error = sys.stderr.getvalue() + if output: + if not output.endswith('\n'): + output += '\n' + msgLines.append(STDOUT_LINE % output) + if error: + if not error.endswith('\n'): + error += '\n' + msgLines.append(STDERR_LINE % error) + return ''.join(msgLines) + + def _is_relevant_tb_level(self, tb): + return '__unittest' in tb.tb_frame.f_globals + + def _count_relevant_tb_levels(self, tb): + length = 0 + while tb and not self._is_relevant_tb_level(tb): + length += 1 + tb = tb.tb_next + return length + + def __repr__(self): + return "<%s run=%i errors=%i failures=%i>" % \ + (util.strclass(self.__class__), self.testsRun, len(self.errors), + len(self.failures)) diff --git a/third_party/Python/module/unittest2/unittest2/runner.py b/third_party/Python/module/unittest2/unittest2/runner.py new file mode 100644 index 000000000000..db0f89d26ec4 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/runner.py @@ -0,0 +1,203 @@ +"""Running tests""" + +import sys +import time +import unittest +import progress + +from unittest2 import result + +try: + from unittest2.signals import registerResult +except ImportError: + def registerResult(_): + pass + +__unittest = True + + +class _WritelnDecorator(object): + """Used to decorate file-like objects with a handy 'writeln' method""" + def __init__(self,stream): + self.stream = stream + + def __getattr__(self, attr): + if attr in ('stream', '__getstate__'): + raise AttributeError(attr) + return getattr(self.stream,attr) + + def writeln(self, arg=None): + if arg: + self.write(arg) + self.write('\n') # text-mode streams translate to \r\n if needed + + +class TextTestResult(result.TestResult): + """A test result class that can print formatted text results to a stream. + + Used by TextTestRunner. + """ + separator1 = '=' * 70 + separator2 = '-' * 70 + + def __init__(self, stream, descriptions, verbosity): + super(TextTestResult, self).__init__() + self.stream = stream + self.showAll = verbosity > 1 + self.dots = verbosity == 1 + self.descriptions = descriptions + self.progressbar = None + + if self.dots: + self.stream.writeln(".=success F=fail E=error s=skipped x=expected-fail u=unexpected-success"); + self.stream.writeln(""); + self.stream.flush() + + def getDescription(self, test): + doc_first_line = test.shortDescription() + if self.descriptions and doc_first_line: + return '\n'.join((str(test), doc_first_line)) + else: + return str(test) + + def startTest(self, test): + super(TextTestResult, self).startTest(test) + if self.showAll: + self.stream.write(self.getDescription(test)) + self.stream.write(" ... ") + self.stream.flush() + + def newTestResult(self,test,result_short,result_long): + if self.showAll: + self.stream.writeln(result_long) + elif self.progressbar: + self.progressbar.__add__(1) + self.progressbar.add_event(result_short) + self.progressbar.show_progress() + elif self.dots: + self.stream.write(result_short) + self.stream.flush() + + def addSuccess(self, test): + super(TextTestResult, self).addSuccess(test) + if self.progressbar: + self.newTestResult(test,"ok","ok") + else: + self.newTestResult(test,".","ok") + + def addError(self, test, err): + super(TextTestResult, self).addError(test, err) + self.newTestResult(test,"E","ERROR") + + def addFailure(self, test, err): + super(TextTestResult, self).addFailure(test, err) + self.newTestResult(test,"F","FAILURE") + + def addSkip(self, test, reason): + super(TextTestResult, self).addSkip(test, reason) + self.newTestResult(test,"s","skipped %r" % (reason,)) + + def addExpectedFailure(self, test, err, bugnumber): + super(TextTestResult, self).addExpectedFailure(test, err, bugnumber) + self.newTestResult(test,"x","expected failure") + + def addUnexpectedSuccess(self, test, bugnumber): + super(TextTestResult, self).addUnexpectedSuccess(test, bugnumber) + self.newTestResult(test,"u","unexpected success") + + def printErrors(self): + if self.progressbar: + self.progressbar.complete() + self.progressbar.show_progress() + if self.dots or self.showAll: + self.stream.writeln() + self.printErrorList('ERROR', self.errors) + self.printErrorList('FAIL', self.failures) + + def printErrorList(self, flavour, errors): + for test, err in errors: + self.stream.writeln(self.separator1) + self.stream.writeln("%s: %s" % (flavour, self.getDescription(test))) + self.stream.writeln(self.separator2) + self.stream.writeln("%s" % err) + + def stopTestRun(self): + super(TextTestResult, self).stopTestRun() + self.printErrors() + + +class TextTestRunner(unittest.TextTestRunner): + """A test runner class that displays results in textual form. + + It prints out the names of tests as they are run, errors as they + occur, and a summary of the results at the end of the test run. + """ + resultclass = TextTestResult + + def __init__(self, stream=sys.stderr, descriptions=True, verbosity=1, + failfast=False, buffer=False, resultclass=None): + self.stream = _WritelnDecorator(stream) + self.descriptions = descriptions + self.verbosity = verbosity + self.failfast = failfast + self.buffer = buffer + if resultclass is not None: + self.resultclass = resultclass + + def _makeResult(self): + return self.resultclass(self.stream, self.descriptions, self.verbosity) + + def run(self, test): + "Run the given test case or test suite." + result = self._makeResult() + result.failfast = self.failfast + result.buffer = self.buffer + registerResult(result) + + startTime = time.time() + startTestRun = getattr(result, 'startTestRun', None) + if startTestRun is not None: + startTestRun() + try: + test(result) + finally: + stopTestRun = getattr(result, 'stopTestRun', None) + if stopTestRun is not None: + stopTestRun() + else: + result.printErrors() + stopTime = time.time() + timeTaken = stopTime - startTime + if hasattr(result, 'separator2'): + self.stream.writeln(result.separator2) + run = result.testsRun + self.stream.writeln("Ran %d test%s in %.3fs" % + (run, run != 1 and "s" or "", timeTaken)) + self.stream.writeln() + + expectedFails = unexpectedSuccesses = skipped = passed = failed = errored = 0 + try: + results = map(len, (result.expectedFailures, + result.unexpectedSuccesses, + result.skipped, + result.passes, + result.failures, + result.errors)) + expectedFails, unexpectedSuccesses, skipped, passed, failed, errored = results + except AttributeError: + pass + infos = [] + infos.append("%d passes" % passed) + infos.append("%d failures" % failed) + infos.append("%d errors" % errored) + infos.append("%d skipped" % skipped) + infos.append("%d expected failures" % expectedFails) + infos.append("%d unexpected successes" % unexpectedSuccesses) + self.stream.write("RESULT: ") + if not result.wasSuccessful(): + self.stream.write("FAILED") + else: + self.stream.write("PASSED") + + self.stream.writeln(" (%s)" % (", ".join(infos),)) + return result diff --git a/third_party/Python/module/unittest2/unittest2/signals.py b/third_party/Python/module/unittest2/unittest2/signals.py new file mode 100644 index 000000000000..e40328dea7a9 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/signals.py @@ -0,0 +1,57 @@ +import signal +import weakref + +from unittest2.compatibility import wraps + +__unittest = True + + +class _InterruptHandler(object): + def __init__(self, default_handler): + self.called = False + self.default_handler = default_handler + + def __call__(self, signum, frame): + installed_handler = signal.getsignal(signal.SIGINT) + if installed_handler is not self: + # if we aren't the installed handler, then delegate immediately + # to the default handler + self.default_handler(signum, frame) + + if self.called: + self.default_handler(signum, frame) + self.called = True + for result in _results.keys(): + result.stop() + +_results = weakref.WeakKeyDictionary() +def registerResult(result): + _results[result] = 1 + +def removeResult(result): + return bool(_results.pop(result, None)) + +_interrupt_handler = None +def installHandler(): + global _interrupt_handler + if _interrupt_handler is None: + default_handler = signal.getsignal(signal.SIGINT) + _interrupt_handler = _InterruptHandler(default_handler) + signal.signal(signal.SIGINT, _interrupt_handler) + + +def removeHandler(method=None): + if method is not None: + @wraps(method) + def inner(*args, **kwargs): + initial = signal.getsignal(signal.SIGINT) + removeHandler() + try: + return method(*args, **kwargs) + finally: + signal.signal(signal.SIGINT, initial) + return inner + + global _interrupt_handler + if _interrupt_handler is not None: + signal.signal(signal.SIGINT, _interrupt_handler.default_handler) diff --git a/third_party/Python/module/unittest2/unittest2/suite.py b/third_party/Python/module/unittest2/unittest2/suite.py new file mode 100644 index 000000000000..9fda66aa09e5 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/suite.py @@ -0,0 +1,288 @@ +"""TestSuite""" + +import sys +import unittest +from unittest2 import case, util +import six + +__unittest = True + + +class BaseTestSuite(unittest.TestSuite): + """A simple test suite that doesn't provide class or module shared fixtures. + """ + def __init__(self, tests=()): + self._tests = [] + self.addTests(tests) + + def __repr__(self): + return "<%s tests=%s>" % (util.strclass(self.__class__), list(self)) + + def __eq__(self, other): + if not isinstance(other, self.__class__): + return NotImplemented + return list(self) == list(other) + + def __ne__(self, other): + return not self == other + + # Can't guarantee hash invariant, so flag as unhashable + __hash__ = None + + def __iter__(self): + return iter(self._tests) + + def countTestCases(self): + cases = 0 + for test in self: + cases += test.countTestCases() + return cases + + def addTest(self, test): + # sanity checks + if not hasattr(test, '__call__'): + raise TypeError("%r is not callable" % (repr(test),)) + if isinstance(test, type) and issubclass(test, + (case.TestCase, TestSuite)): + raise TypeError("TestCases and TestSuites must be instantiated " + "before passing them to addTest()") + self._tests.append(test) + + def addTests(self, tests): + if isinstance(tests, six.string_types): + raise TypeError("tests must be an iterable of tests, not a string") + for test in tests: + self.addTest(test) + + def run(self, result): + for test in self: + if result.shouldStop: + break + test(result) + return result + + def __call__(self, *args, **kwds): + return self.run(*args, **kwds) + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + for test in self: + test.debug() + + +class TestSuite(BaseTestSuite): + """A test suite is a composite test consisting of a number of TestCases. + + For use, create an instance of TestSuite, then add test case instances. + When all tests have been added, the suite can be passed to a test + runner, such as TextTestRunner. It will run the individual test cases + in the order in which they were added, aggregating the results. When + subclassing, do not forget to call the base class constructor. + """ + + + def run(self, result): + self._wrapped_run(result) + self._tearDownPreviousClass(None, result) + self._handleModuleTearDown(result) + return result + + def debug(self): + """Run the tests without collecting errors in a TestResult""" + debug = _DebugResult() + self._wrapped_run(debug, True) + self._tearDownPreviousClass(None, debug) + self._handleModuleTearDown(debug) + + ################################ + # private methods + def _wrapped_run(self, result, debug=False): + for test in self: + if result.shouldStop: + break + + if _isnotsuite(test): + self._tearDownPreviousClass(test, result) + self._handleModuleFixture(test, result) + self._handleClassSetUp(test, result) + result._previousTestClass = test.__class__ + + if (getattr(test.__class__, '_classSetupFailed', False) or + getattr(result, '_moduleSetUpFailed', False)): + continue + + if hasattr(test, '_wrapped_run'): + test._wrapped_run(result, debug) + elif not debug: + test(result) + else: + test.debug() + + def _handleClassSetUp(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if result._moduleSetUpFailed: + return + if getattr(currentClass, "__unittest_skip__", False): + return + + try: + currentClass._classSetupFailed = False + except TypeError: + # test may actually be a function + # so its class will be a builtin-type + pass + + setUpClass = getattr(currentClass, 'setUpClass', None) + if setUpClass is not None: + try: + setUpClass() + except Exception as e: + if isinstance(result, _DebugResult): + raise + currentClass._classSetupFailed = True + className = util.strclass(currentClass) + errorName = 'setUpClass (%s)' % className + self._addClassOrModuleLevelException(result, e, errorName) + + def _get_previous_module(self, result): + previousModule = None + previousClass = getattr(result, '_previousTestClass', None) + if previousClass is not None: + previousModule = previousClass.__module__ + return previousModule + + + def _handleModuleFixture(self, test, result): + previousModule = self._get_previous_module(result) + currentModule = test.__class__.__module__ + if currentModule == previousModule: + return + + self._handleModuleTearDown(result) + + + result._moduleSetUpFailed = False + try: + module = sys.modules[currentModule] + except KeyError: + return + setUpModule = getattr(module, 'setUpModule', None) + if setUpModule is not None: + try: + setUpModule() + except Exception as e: + if isinstance(result, _DebugResult): + raise + result._moduleSetUpFailed = True + errorName = 'setUpModule (%s)' % currentModule + self._addClassOrModuleLevelException(result, e, errorName) + + def _addClassOrModuleLevelException(self, result, exception, errorName): + error = _ErrorHolder(errorName) + addSkip = getattr(result, 'addSkip', None) + if addSkip is not None and isinstance(exception, case.SkipTest): + addSkip(error, str(exception)) + else: + result.addError(error, sys.exc_info()) + + def _handleModuleTearDown(self, result): + previousModule = self._get_previous_module(result) + if previousModule is None: + return + if result._moduleSetUpFailed: + return + + try: + module = sys.modules[previousModule] + except KeyError: + return + + tearDownModule = getattr(module, 'tearDownModule', None) + if tearDownModule is not None: + try: + tearDownModule() + except Exception as e: + if isinstance(result, _DebugResult): + raise + errorName = 'tearDownModule (%s)' % previousModule + self._addClassOrModuleLevelException(result, e, errorName) + + def _tearDownPreviousClass(self, test, result): + previousClass = getattr(result, '_previousTestClass', None) + currentClass = test.__class__ + if currentClass == previousClass: + return + if getattr(previousClass, '_classSetupFailed', False): + return + if getattr(result, '_moduleSetUpFailed', False): + return + if getattr(previousClass, "__unittest_skip__", False): + return + + tearDownClass = getattr(previousClass, 'tearDownClass', None) + if tearDownClass is not None: + try: + tearDownClass() + except Exception as e: + if isinstance(result, _DebugResult): + raise + className = util.strclass(previousClass) + errorName = 'tearDownClass (%s)' % className + self._addClassOrModuleLevelException(result, e, errorName) + + +class _ErrorHolder(object): + """ + Placeholder for a TestCase inside a result. As far as a TestResult + is concerned, this looks exactly like a unit test. Used to insert + arbitrary errors into a test suite run. + """ + # Inspired by the ErrorHolder from Twisted: + # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py + + # attribute used by TestResult._exc_info_to_string + failureException = None + + def __init__(self, description): + self.description = description + + def id(self): + return self.description + + def shortDescription(self): + return None + + def __repr__(self): + return "<ErrorHolder description=%r>" % (self.description,) + + def __str__(self): + return self.id() + + def run(self, result): + # could call result.addError(...) - but this test-like object + # shouldn't be run anyway + pass + + def __call__(self, result): + return self.run(result) + + def countTestCases(self): + return 0 + +def _isnotsuite(test): + "A crude way to tell apart testcases and suites with duck-typing" + try: + iter(test) + except TypeError: + return True + return False + + +class _DebugResult(object): + "Used by the TestSuite to hold previous class when running in debug." + _previousTestClass = None + _moduleSetUpFailed = False + shouldStop = False diff --git a/third_party/Python/module/unittest2/unittest2/test/__init__.py b/third_party/Python/module/unittest2/unittest2/test/__init__.py new file mode 100644 index 000000000000..4287ca861797 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/__init__.py @@ -0,0 +1 @@ +#
\ No newline at end of file diff --git a/third_party/Python/module/unittest2/unittest2/test/dummy.py b/third_party/Python/module/unittest2/unittest2/test/dummy.py new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/dummy.py diff --git a/third_party/Python/module/unittest2/unittest2/test/support.py b/third_party/Python/module/unittest2/unittest2/test/support.py new file mode 100644 index 000000000000..eec6eec77907 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/support.py @@ -0,0 +1,177 @@ +import sys +import warnings + +import unittest2 + + +def resultFactory(*_): + return unittest2.TestResult() + +class OldTestResult(object): + """An object honouring TestResult before startTestRun/stopTestRun.""" + + def __init__(self, *_): + self.failures = [] + self.errors = [] + self.testsRun = 0 + self.shouldStop = False + + def startTest(self, test): + pass + + def stopTest(self, test): + pass + + def addError(self, test, err): + self.errors.append((test, err)) + + def addFailure(self, test, err): + self.failures.append((test, err)) + + def addSuccess(self, test): + pass + + def wasSuccessful(self): + return True + + def printErrors(self): + pass + +class LoggingResult(unittest2.TestResult): + def __init__(self, log): + self._events = log + super(LoggingResult, self).__init__() + + def startTest(self, test): + self._events.append('startTest') + super(LoggingResult, self).startTest(test) + + def startTestRun(self): + self._events.append('startTestRun') + super(LoggingResult, self).startTestRun() + + def stopTest(self, test): + self._events.append('stopTest') + super(LoggingResult, self).stopTest(test) + + def stopTestRun(self): + self._events.append('stopTestRun') + super(LoggingResult, self).stopTestRun() + + def addFailure(self, *args): + self._events.append('addFailure') + super(LoggingResult, self).addFailure(*args) + + def addSuccess(self, *args): + self._events.append('addSuccess') + super(LoggingResult, self).addSuccess(*args) + + def addError(self, *args): + self._events.append('addError') + super(LoggingResult, self).addError(*args) + + def addSkip(self, *args): + self._events.append('addSkip') + super(LoggingResult, self).addSkip(*args) + + def addExpectedFailure(self, *args): + self._events.append('addExpectedFailure') + super(LoggingResult, self).addExpectedFailure(*args) + + def addUnexpectedSuccess(self, *args): + self._events.append('addUnexpectedSuccess') + super(LoggingResult, self).addUnexpectedSuccess(*args) + + +class EqualityMixin(object): + """Used as a mixin for TestCase""" + + # Check for a valid __eq__ implementation + def test_eq(self): + for obj_1, obj_2 in self.eq_pairs: + self.assertEqual(obj_1, obj_2) + self.assertEqual(obj_2, obj_1) + + # Check for a valid __ne__ implementation + def test_ne(self): + for obj_1, obj_2 in self.ne_pairs: + self.assertNotEqual(obj_1, obj_2) + self.assertNotEqual(obj_2, obj_1) + +class HashingMixin(object): + """Used as a mixin for TestCase""" + + # Check for a valid __hash__ implementation + def test_hash(self): + for obj_1, obj_2 in self.eq_pairs: + try: + if not hash(obj_1) == hash(obj_2): + self.fail("%r and %r do not hash equal" % (obj_1, obj_2)) + except KeyboardInterrupt: + raise + except Exception as e: + self.fail("Problem hashing %r and %r: %s" % (obj_1, obj_2, e)) + + for obj_1, obj_2 in self.ne_pairs: + try: + if hash(obj_1) == hash(obj_2): + self.fail("%s and %s hash equal, but shouldn't" % + (obj_1, obj_2)) + except KeyboardInterrupt: + raise + except Exception as e: + self.fail("Problem hashing %s and %s: %s" % (obj_1, obj_2, e)) + + + +# copied from Python 2.6 +try: + from warnings import catch_warnings +except ImportError: + class catch_warnings(object): + def __init__(self, record=False, module=None): + self._record = record + self._module = sys.modules['warnings'] + self._entered = False + + def __repr__(self): + args = [] + if self._record: + args.append("record=True") + name = type(self).__name__ + return "%s(%s)" % (name, ", ".join(args)) + + def __enter__(self): + if self._entered: + raise RuntimeError("Cannot enter %r twice" % self) + self._entered = True + self._filters = self._module.filters + self._module.filters = self._filters[:] + self._showwarning = self._module.showwarning + if self._record: + log = [] + def showwarning(*args, **kwargs): + log.append(WarningMessage(*args, **kwargs)) + self._module.showwarning = showwarning + return log + else: + return None + + def __exit__(self, *exc_info): + if not self._entered: + raise RuntimeError("Cannot exit %r without entering first" % self) + self._module.filters = self._filters + self._module.showwarning = self._showwarning + + class WarningMessage(object): + _WARNING_DETAILS = ("message", "category", "filename", "lineno", "file", + "line") + def __init__(self, message, category, filename, lineno, file=None, + line=None): + local_values = locals() + for attr in self._WARNING_DETAILS: + setattr(self, attr, local_values[attr]) + self._category_name = None + if category.__name__: + self._category_name = category.__name__ + diff --git a/third_party/Python/module/unittest2/unittest2/test/test_assertions.py b/third_party/Python/module/unittest2/unittest2/test/test_assertions.py new file mode 100644 index 000000000000..d71326b218a9 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_assertions.py @@ -0,0 +1,254 @@ +import datetime + +import unittest2 + + +class Test_Assertions(unittest2.TestCase): + def test_AlmostEqual(self): + self.assertAlmostEqual(1.00000001, 1.0) + self.assertNotAlmostEqual(1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.0000001, 1.0) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 1.00000001, 1.0) + + self.assertAlmostEqual(1.1, 1.0, places=0) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 1.1, 1.0, places=1) + + self.assertAlmostEqual(0, .1+.1j, places=0) + self.assertNotAlmostEqual(0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertAlmostEqual, 0, .1+.1j, places=1) + self.assertRaises(self.failureException, + self.assertNotAlmostEqual, 0, .1+.1j, places=0) + + try: + self.assertAlmostEqual(float('inf'), float('inf')) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + float('inf'), float('inf')) + except ValueError: + # float('inf') is invalid on Windows in Python 2.4 / 2.5 + x = object() + self.assertAlmostEqual(x, x) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + x, x) + + + def test_AmostEqualWithDelta(self): + self.assertAlmostEqual(1.1, 1.0, delta=0.5) + self.assertAlmostEqual(1.0, 1.1, delta=0.5) + self.assertNotAlmostEqual(1.1, 1.0, delta=0.05) + self.assertNotAlmostEqual(1.0, 1.1, delta=0.05) + + self.assertRaises(self.failureException, self.assertAlmostEqual, + 1.1, 1.0, delta=0.05) + self.assertRaises(self.failureException, self.assertNotAlmostEqual, + 1.1, 1.0, delta=0.5) + + self.assertRaises(TypeError, self.assertAlmostEqual, + 1.1, 1.0, places=2, delta=2) + self.assertRaises(TypeError, self.assertNotAlmostEqual, + 1.1, 1.0, places=2, delta=2) + + first = datetime.datetime.now() + second = first + datetime.timedelta(seconds=10) + self.assertAlmostEqual(first, second, + delta=datetime.timedelta(seconds=20)) + self.assertNotAlmostEqual(first, second, + delta=datetime.timedelta(seconds=5)) + + def testAssertNotRegexpMatches(self): + self.assertNotRegexpMatches('Ala ma kota', r'r+') + try: + self.assertNotRegexpMatches('Ala ma kota', r'k.t', 'Message') + except self.failureException as e: + self.assertIn("'kot'", e.args[0]) + self.assertIn('Message', e.args[0]) + else: + self.fail('assertNotRegexpMatches should have failed.') + + +class TestLongMessage(unittest2.TestCase): + """Test that the individual asserts honour longMessage. + This actually tests all the message behaviour for + asserts that use longMessage.""" + + def setUp(self): + class TestableTestFalse(unittest2.TestCase): + longMessage = False + failureException = self.failureException + + def testTest(self): + pass + + class TestableTestTrue(unittest2.TestCase): + longMessage = True + failureException = self.failureException + + def testTest(self): + pass + + self.testableTrue = TestableTestTrue('testTest') + self.testableFalse = TestableTestFalse('testTest') + + def testDefault(self): + self.assertTrue(unittest2.TestCase.longMessage) + + def test_formatMsg(self): + self.assertEquals(self.testableFalse._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableFalse._formatMessage("foo", "bar"), "foo") + + self.assertEquals(self.testableTrue._formatMessage(None, "foo"), "foo") + self.assertEquals(self.testableTrue._formatMessage("foo", "bar"), "bar : foo") + + # This blows up if _formatMessage uses string concatenation + self.testableTrue._formatMessage(object(), 'foo') + + def assertMessages(self, methodName, args, errors): + def getMethod(i): + useTestableFalse = i < 2 + if useTestableFalse: + test = self.testableFalse + else: + test = self.testableTrue + return getattr(test, methodName) + + for i, expected_regexp in enumerate(errors): + testMethod = getMethod(i) + kwargs = {} + withMsg = i % 2 + if withMsg: + kwargs = {"msg": "oops"} + + self.assertRaisesRegexp(self.failureException, + expected_regexp, + lambda: testMethod(*args, **kwargs)) + + def testAssertTrue(self): + self.assertMessages('assertTrue', (False,), + ["^False is not True$", "^oops$", "^False is not True$", + "^False is not True : oops$"]) + + def testAssertFalse(self): + self.assertMessages('assertFalse', (True,), + ["^True is not False$", "^oops$", "^True is not False$", + "^True is not False : oops$"]) + + def testNotEqual(self): + self.assertMessages('assertNotEqual', (1, 1), + ["^1 == 1$", "^oops$", "^1 == 1$", + "^1 == 1 : oops$"]) + + def testAlmostEqual(self): + self.assertMessages('assertAlmostEqual', (1, 2), + ["^1 != 2 within 7 places$", "^oops$", + "^1 != 2 within 7 places$", "^1 != 2 within 7 places : oops$"]) + + def testNotAlmostEqual(self): + self.assertMessages('assertNotAlmostEqual', (1, 1), + ["^1 == 1 within 7 places$", "^oops$", + "^1 == 1 within 7 places$", "^1 == 1 within 7 places : oops$"]) + + def test_baseAssertEqual(self): + self.assertMessages('_baseAssertEqual', (1, 2), + ["^1 != 2$", "^oops$", "^1 != 2$", "^1 != 2 : oops$"]) + + def testAssertSequenceEqual(self): + # Error messages are multiline so not testing on full message + # assertTupleEqual and assertListEqual delegate to this method + self.assertMessages('assertSequenceEqual', ([], [None]), + ["\+ \[None\]$", "^oops$", r"\+ \[None\]$", + r"\+ \[None\] : oops$"]) + + def testAssertSetEqual(self): + self.assertMessages('assertSetEqual', (set(), set([None])), + ["None$", "^oops$", "None$", + "None : oops$"]) + + def testAssertIn(self): + self.assertMessages('assertIn', (None, []), + ['^None not found in \[\]$', "^oops$", + '^None not found in \[\]$', + '^None not found in \[\] : oops$']) + + def testAssertNotIn(self): + self.assertMessages('assertNotIn', (None, [None]), + ['^None unexpectedly found in \[None\]$', "^oops$", + '^None unexpectedly found in \[None\]$', + '^None unexpectedly found in \[None\] : oops$']) + + def testAssertDictEqual(self): + self.assertMessages('assertDictEqual', ({}, {'key': 'value'}), + [r"\+ \{'key': 'value'\}$", "^oops$", + "\+ \{'key': 'value'\}$", + "\+ \{'key': 'value'\} : oops$"]) + + def testAssertDictContainsSubset(self): + self.assertMessages('assertDictContainsSubset', ({'key': 'value'}, {}), + ["^Missing: 'key'$", "^oops$", + "^Missing: 'key'$", + "^Missing: 'key' : oops$"]) + + def testAssertItemsEqual(self): + self.assertMessages('assertItemsEqual', ([], [None]), + [r"\[None\]$", "^oops$", + r"\[None\]$", + r"\[None\] : oops$"]) + + def testAssertMultiLineEqual(self): + self.assertMessages('assertMultiLineEqual', ("", "foo"), + [r"\+ foo$", "^oops$", + r"\+ foo$", + r"\+ foo : oops$"]) + + def testAssertLess(self): + self.assertMessages('assertLess', (2, 1), + ["^2 not less than 1$", "^oops$", + "^2 not less than 1$", "^2 not less than 1 : oops$"]) + + def testAssertLessEqual(self): + self.assertMessages('assertLessEqual', (2, 1), + ["^2 not less than or equal to 1$", "^oops$", + "^2 not less than or equal to 1$", + "^2 not less than or equal to 1 : oops$"]) + + def testAssertGreater(self): + self.assertMessages('assertGreater', (1, 2), + ["^1 not greater than 2$", "^oops$", + "^1 not greater than 2$", + "^1 not greater than 2 : oops$"]) + + def testAssertGreaterEqual(self): + self.assertMessages('assertGreaterEqual', (1, 2), + ["^1 not greater than or equal to 2$", "^oops$", + "^1 not greater than or equal to 2$", + "^1 not greater than or equal to 2 : oops$"]) + + def testAssertIsNone(self): + self.assertMessages('assertIsNone', ('not None',), + ["^'not None' is not None$", "^oops$", + "^'not None' is not None$", + "^'not None' is not None : oops$"]) + + def testAssertIsNotNone(self): + self.assertMessages('assertIsNotNone', (None,), + ["^unexpectedly None$", "^oops$", + "^unexpectedly None$", + "^unexpectedly None : oops$"]) + + def testAssertIs(self): + self.assertMessages('assertIs', (None, 'foo'), + ["^None is not 'foo'$", "^oops$", + "^None is not 'foo'$", + "^None is not 'foo' : oops$"]) + + def testAssertIsNot(self): + self.assertMessages('assertIsNot', (None, None), + ["^unexpectedly identical: None$", "^oops$", + "^unexpectedly identical: None$", + "^unexpectedly identical: None : oops$"]) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_break.py b/third_party/Python/module/unittest2/unittest2/test/test_break.py new file mode 100644 index 000000000000..1f7864b7a415 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_break.py @@ -0,0 +1,260 @@ +import gc +import os +import weakref + +from cStringIO import StringIO + +try: + import signal +except ImportError: + signal = None + +import unittest2 + + +class TestBreak(unittest2.TestCase): + + def setUp(self): + self._default_handler = signal.getsignal(signal.SIGINT) + + def tearDown(self): + signal.signal(signal.SIGINT, self._default_handler) + unittest2.signals._results = weakref.WeakKeyDictionary() + unittest2.signals._interrupt_handler = None + + + def testInstallHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(unittest2.signals._interrupt_handler.called) + + def testRegisterResult(self): + result = unittest2.TestResult() + unittest2.registerResult(result) + + for ref in unittest2.signals._results: + if ref is result: + break + elif ref is not result: + self.fail("odd object in result set") + else: + self.fail("result not found") + + + def testInterruptCaught(self): + default_handler = signal.getsignal(signal.SIGINT) + + result = unittest2.TestResult() + unittest2.installHandler() + unittest2.registerResult(result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + self.assertTrue(result.breakCaught) + + + def testSecondInterrupt(self): + result = unittest2.TestResult() + unittest2.installHandler() + unittest2.registerResult(result) + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + result.breakCaught = True + self.assertTrue(result.shouldStop) + os.kill(pid, signal.SIGINT) + self.fail("Second KeyboardInterrupt not raised") + + try: + test(result) + except KeyboardInterrupt: + pass + else: + self.fail("Second KeyboardInterrupt not raised") + self.assertTrue(result.breakCaught) + + + def testTwoResults(self): + unittest2.installHandler() + + result = unittest2.TestResult() + unittest2.registerResult(result) + new_handler = signal.getsignal(signal.SIGINT) + + result2 = unittest2.TestResult() + unittest2.registerResult(result2) + self.assertEqual(signal.getsignal(signal.SIGINT), new_handler) + + result3 = unittest2.TestResult() + + def test(result): + pid = os.getpid() + os.kill(pid, signal.SIGINT) + + try: + test(result) + except KeyboardInterrupt: + self.fail("KeyboardInterrupt not handled") + + self.assertTrue(result.shouldStop) + self.assertTrue(result2.shouldStop) + self.assertFalse(result3.shouldStop) + + + def testHandlerReplacedButCalled(self): + # If our handler has been replaced (is no longer installed) but is + # called by the *new* handler, then it isn't safe to delay the + # SIGINT and we should immediately delegate to the default handler + unittest2.installHandler() + + handler = signal.getsignal(signal.SIGINT) + def new_handler(frame, signum): + handler(frame, signum) + signal.signal(signal.SIGINT, new_handler) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + else: + self.fail("replaced but delegated handler doesn't raise interrupt") + + def testRunner(self): + # Creating a TextTestRunner with the appropriate argument should + # register the TextTestResult it creates + runner = unittest2.TextTestRunner(stream=StringIO()) + + result = runner.run(unittest2.TestSuite()) + self.assertIn(result, unittest2.signals._results) + + def testWeakReferences(self): + # Calling registerResult on a result should not keep it alive + result = unittest2.TestResult() + unittest2.registerResult(result) + + ref = weakref.ref(result) + del result + + # For non-reference counting implementations + gc.collect();gc.collect() + self.assertIsNone(ref()) + + + def testRemoveResult(self): + result = unittest2.TestResult() + unittest2.registerResult(result) + + unittest2.installHandler() + self.assertTrue(unittest2.removeResult(result)) + + # Should this raise an error instead? + self.assertFalse(unittest2.removeResult(unittest2.TestResult())) + + try: + pid = os.getpid() + os.kill(pid, signal.SIGINT) + except KeyboardInterrupt: + pass + + self.assertFalse(result.shouldStop) + + def testMainInstallsHandler(self): + failfast = object() + test = object() + verbosity = object() + result = object() + default_handler = signal.getsignal(signal.SIGINT) + + class FakeRunner(object): + initArgs = [] + runArgs = [] + def __init__(self, *args, **kwargs): + self.initArgs.append((args, kwargs)) + def run(self, test): + self.runArgs.append(test) + return result + + class Program(unittest2.TestProgram): + def __init__(self, catchbreak): + self.exit = False + self.verbosity = verbosity + self.failfast = failfast + self.catchbreak = catchbreak + self.testRunner = FakeRunner + self.test = test + self.result = None + + p = Program(False) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'verbosity': verbosity, + 'failfast': failfast, + 'buffer': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + FakeRunner.initArgs = [] + FakeRunner.runArgs = [] + p = Program(True) + p.runTests() + + self.assertEqual(FakeRunner.initArgs, [((), {'verbosity': verbosity, + 'failfast': failfast, + 'buffer': None})]) + self.assertEqual(FakeRunner.runArgs, [test]) + self.assertEqual(p.result, result) + + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + + def testRemoveHandler(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + unittest2.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + # check that calling removeHandler multiple times has no ill-effect + unittest2.removeHandler() + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + def testRemoveHandlerAsDecorator(self): + default_handler = signal.getsignal(signal.SIGINT) + unittest2.installHandler() + + @unittest2.removeHandler + def test(): + self.assertEqual(signal.getsignal(signal.SIGINT), default_handler) + + test() + self.assertNotEqual(signal.getsignal(signal.SIGINT), default_handler) + + +# Should also skip some tests on Jython +skipper = unittest2.skipUnless(hasattr(os, 'kill') and signal is not None, + "test uses os.kill(...) and the signal module") +TestBreak = skipper(TestBreak) + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_case.py b/third_party/Python/module/unittest2/unittest2/test/test_case.py new file mode 100644 index 000000000000..5f310ecb046e --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_case.py @@ -0,0 +1,1066 @@ +import difflib +import pprint +import re +import six + +from copy import deepcopy + +import unittest2 + +from unittest2.test.support import ( + OldTestResult, EqualityMixin, HashingMixin, LoggingResult +) + + +class MyException(Exception): + pass + + +class Test(object): + "Keep these TestCase classes out of the main namespace" + + class Foo(unittest2.TestCase): + def runTest(self): pass + def test1(self): pass + + class Bar(Foo): + def test2(self): pass + + class LoggingTestCase(unittest2.TestCase): + """A test case which logs its calls.""" + + def __init__(self, events): + super(Test.LoggingTestCase, self).__init__('test') + self.events = events + + def setUp(self): + self.events.append('setUp') + + def test(self): + self.events.append('test') + + def tearDown(self): + self.events.append('tearDown') + + + +class TestCleanUp(unittest2.TestCase): + + def testCleanUp(self): + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + self.assertEqual(test._cleanups, []) + + cleanups = [] + + def cleanup1(*args, **kwargs): + cleanups.append((1, args, kwargs)) + + def cleanup2(*args, **kwargs): + cleanups.append((2, args, kwargs)) + + test.addCleanup(cleanup1, 1, 2, 3, four='hello', five='goodbye') + test.addCleanup(cleanup2) + + self.assertEqual(test._cleanups, + [(cleanup1, (1, 2, 3), dict(four='hello', five='goodbye')), + (cleanup2, (), {})]) + + result = test.doCleanups() + self.assertTrue(result) + + self.assertEqual(cleanups, [(2, (), {}), (1, (1, 2, 3), dict(four='hello', five='goodbye'))]) + + def testCleanUpWithErrors(self): + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + class MockResult(object): + errors = [] + def addError(self, test, exc_info): + self.errors.append((test, exc_info)) + + result = MockResult() + test = TestableTest('testNothing') + test._resultForDoCleanups = result + + exc1 = Exception('foo') + exc2 = Exception('bar') + def cleanup1(): + raise exc1 + + def cleanup2(): + raise exc2 + + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + self.assertFalse(test.doCleanups()) + + (test1, (Type1, instance1, _)), (test2, (Type2, instance2, _)) = reversed(MockResult.errors) + self.assertEqual((test1, Type1, instance1), (test, Exception, exc1)) + self.assertEqual((test2, Type2, instance2), (test, Exception, exc2)) + + def testCleanupInRun(self): + blowUp = False + ordering = [] + + class TestableTest(unittest2.TestCase): + def setUp(self): + ordering.append('setUp') + if blowUp: + raise Exception('foo') + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + def cleanup2(): + ordering.append('cleanup2') + test.addCleanup(cleanup1) + test.addCleanup(cleanup2) + + def success(some_test): + self.assertEqual(some_test, test) + ordering.append('success') + + result = unittest2.TestResult() + result.addSuccess = success + + test.run(result) + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', + 'cleanup2', 'cleanup1', 'success']) + + blowUp = True + ordering = [] + test = TestableTest('testNothing') + test.addCleanup(cleanup1) + test.run(result) + self.assertEqual(ordering, ['setUp', 'cleanup1']) + + def testTestCaseDebugExecutesCleanups(self): + ordering = [] + + class TestableTest(unittest2.TestCase): + def setUp(self): + ordering.append('setUp') + self.addCleanup(cleanup1) + + def testNothing(self): + ordering.append('test') + + def tearDown(self): + ordering.append('tearDown') + + test = TestableTest('testNothing') + + def cleanup1(): + ordering.append('cleanup1') + test.addCleanup(cleanup2) + def cleanup2(): + ordering.append('cleanup2') + + test.debug() + self.assertEqual(ordering, ['setUp', 'test', 'tearDown', 'cleanup1', 'cleanup2']) + + +class Test_TestCase(unittest2.TestCase, EqualityMixin, HashingMixin): + + ### Set up attributes used by inherited tests + ################################################################ + + # Used by HashingMixin.test_hash and EqualityMixin.test_eq + eq_pairs = [(Test.Foo('test1'), Test.Foo('test1'))] + + # Used by EqualityMixin.test_ne + ne_pairs = [(Test.Foo('test1'), Test.Foo('runTest')), + (Test.Foo('test1'), Test.Bar('test1')), + (Test.Foo('test1'), Test.Bar('test2'))] + + ################################################################ + ### /Set up attributes used by inherited tests + + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + # ... + # "methodName defaults to "runTest"." + # + # Make sure it really is optional, and that it defaults to the proper + # thing. + def test_init__no_test_name(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test().id()[-13:], '.Test.runTest') + + # "class TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__valid(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + self.assertEqual(Test('test').id()[-10:], '.Test.test') + + # "class unittest2.TestCase([methodName])" + # ... + # "Each instance of TestCase will run a single test method: the + # method named methodName." + def test_init__test_name__invalid(self): + class Test(unittest2.TestCase): + def runTest(self): raise MyException() + def test(self): pass + + try: + Test('testfoo') + except ValueError: + pass + else: + self.fail("Failed to raise ValueError") + + # "Return the number of tests represented by the this test object. For + # TestCase instances, this will always be 1" + def test_countTestCases(self): + class Foo(unittest2.TestCase): + def test(self): pass + + self.assertEqual(Foo('test').countTestCases(), 1) + + # "Return the default type of test result object to be used to run this + # test. For TestCase instances, this will always be + # unittest2.TestResult; subclasses of TestCase should + # override this as necessary." + def test_defaultTestResult(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + result = Foo().defaultTestResult() + self.assertEqual(type(result), unittest2.TestResult) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + self.assertEqual(events, expected) + + # "With a temporary result stopTestRun is called when setUp errors. + def test_run_call_order__error_in_setUp_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def setUp(self): + super(Foo, self).setUp() + raise RuntimeError('raised by Foo.setUp') + + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'addError', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown', + 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "With a default result, an error in the test still results in stopTestRun + # being called." + def test_run_call_order__error_in_test_default_result(self): + events = [] + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + + def test(self): + super(Foo, self).test() + raise RuntimeError('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addError', + 'tearDown', 'stopTest', 'stopTestRun'] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown', + 'stopTest'] + Foo(events).run(result) + self.assertEqual(events, expected) + + # "When a test fails with a default result stopTestRun is still called." + def test_run_call_order__failure_in_test_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def test(self): + super(Foo, self).test() + self.fail('raised by Foo.test') + + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'addFailure', + 'tearDown', 'stopTest', 'stopTestRun'] + events = [] + Foo(events).run() + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + class Foo(Test.LoggingTestCase): + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + Foo(events).run(result) + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + self.assertEqual(events, expected) + + # "When tearDown errors with a default result stopTestRun is still called." + def test_run_call_order__error_in_tearDown_default_result(self): + + class Foo(Test.LoggingTestCase): + def defaultTestResult(self): + return LoggingResult(self.events) + def tearDown(self): + super(Foo, self).tearDown() + raise RuntimeError('raised by Foo.tearDown') + + events = [] + Foo(events).run() + expected = ['startTestRun', 'startTest', 'setUp', 'test', 'tearDown', + 'addError', 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + # "TestCase.run() still works when the defaultTestResult is a TestResult + # that does not support startTestRun and stopTestRun. + def test_run_call_order_default_result(self): + + class Foo(unittest2.TestCase): + def defaultTestResult(self): + return OldTestResult() + def test(self): + pass + + Foo('test').run() + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework. The initial value of this + # attribute is AssertionError" + def test_failureException__default(self): + class Foo(unittest2.TestCase): + def test(self): + pass + + self.assertTrue(Foo('test').failureException is AssertionError) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__explicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest2.TestCase): + def test(self): + raise RuntimeError() + + failureException = RuntimeError + + self.assertTrue(Foo('test').failureException is RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "This class attribute gives the exception raised by the test() method. + # If a test framework needs to use a specialized exception, possibly to + # carry additional information, it must subclass this exception in + # order to ``play fair'' with the framework." + # + # Make sure TestCase.run() respects the designated failureException + def test_failureException__subclassing__implicit_raise(self): + events = [] + result = LoggingResult(events) + + class Foo(unittest2.TestCase): + def test(self): + self.fail("foo") + + failureException = RuntimeError + + self.assertTrue(Foo('test').failureException is RuntimeError) + + + Foo('test').run(result) + expected = ['startTest', 'addFailure', 'stopTest'] + self.assertEqual(events, expected) + + # "The default implementation does nothing." + def test_setUp(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().setUp() + + # "The default implementation does nothing." + def test_tearDown(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + # ... and nothing should happen + Foo().tearDown() + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + self.assertIsInstance(Foo().id(), six.string_types) + + # "If result is omitted or None, a temporary result object is created + # and used, but is not made available to the caller. As TestCase owns the + # temporary result startTestRun and stopTestRun are called. + + def test_run__uses_defaultTestResult(self): + events = [] + + class Foo(unittest2.TestCase): + def test(self): + events.append('test') + + def defaultTestResult(self): + return LoggingResult(events) + + # Make run() find a result object on its own + Foo('test').run() + + expected = ['startTestRun', 'startTest', 'test', 'addSuccess', + 'stopTest', 'stopTestRun'] + self.assertEqual(events, expected) + + def testShortDescriptionWithoutDocstring(self): + self.assertIsNone(self.shortDescription()) + + def testShortDescriptionWithOneLineDocstring(self): + """Tests shortDescription() for a method with a docstring.""" + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a docstring.') + + def testShortDescriptionWithMultiLineDocstring(self): + """Tests shortDescription() for a method with a longer docstring. + + This method ensures that only the first line of a docstring is + returned used in the short description, no matter how long the + whole thing is. + """ + self.assertEqual( + self.shortDescription(), + 'Tests shortDescription() for a method with a longer ' + 'docstring.') + + def testAddTypeEqualityFunc(self): + class SadSnake(object): + """Dummy class for test_addTypeEqualityFunc.""" + s1, s2 = SadSnake(), SadSnake() + self.assertNotEqual(s1, s2) + def AllSnakesCreatedEqual(a, b, msg=None): + return type(a) is type(b) is SadSnake + self.addTypeEqualityFunc(SadSnake, AllSnakesCreatedEqual) + self.assertEqual(s1, s2) + # No this doesn't clean up and remove the SadSnake equality func + # from this TestCase instance but since its a local nothing else + # will ever notice that. + + def testAssertIs(self): + thing = object() + self.assertIs(thing, thing) + self.assertRaises(self.failureException, self.assertIs, thing, object()) + + def testAssertIsNot(self): + thing = object() + self.assertIsNot(thing, object()) + self.assertRaises(self.failureException, self.assertIsNot, thing, thing) + + def testAssertIsInstance(self): + thing = [] + self.assertIsInstance(thing, list) + self.assertRaises(self.failureException, self.assertIsInstance, + thing, dict) + + def testAssertNotIsInstance(self): + thing = [] + self.assertNotIsInstance(thing, dict) + self.assertRaises(self.failureException, self.assertNotIsInstance, + thing, list) + + def testAssertIn(self): + animals = {'monkey': 'banana', 'cow': 'grass', 'seal': 'fish'} + + self.assertIn('a', 'abc') + self.assertIn(2, [1, 2, 3]) + self.assertIn('monkey', animals) + + self.assertNotIn('d', 'abc') + self.assertNotIn(0, [1, 2, 3]) + self.assertNotIn('otter', animals) + + self.assertRaises(self.failureException, self.assertIn, 'x', 'abc') + self.assertRaises(self.failureException, self.assertIn, 4, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertIn, 'elephant', + animals) + + self.assertRaises(self.failureException, self.assertNotIn, 'c', 'abc') + self.assertRaises(self.failureException, self.assertNotIn, 1, [1, 2, 3]) + self.assertRaises(self.failureException, self.assertNotIn, 'cow', + animals) + + def testAssertDictContainsSubset(self): + self.assertDictContainsSubset({}, {}) + self.assertDictContainsSubset({}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1}) + self.assertDictContainsSubset({'a': 1}, {'a': 1, 'b': 2}) + self.assertDictContainsSubset({'a': 1, 'b': 2}, {'a': 1, 'b': 2}) + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 2}, {'a': 1}, + '.*Mismatched values:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'c': 1}, {'a': 1}, + '.*Missing:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*') + + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictContainsSubset, {'a': 1, 'c': 1}, + {'a': 1}, '.*Missing:.*Mismatched values:.*') + + self.assertRaises(self.failureException, + self.assertDictContainsSubset, {1: "one"}, {}) + + def testAssertEqual(self): + equal_pairs = [ + ((), ()), + ({}, {}), + ([], []), + (set(), set()), + (frozenset(), frozenset())] + for a, b in equal_pairs: + # This mess of try excepts is to test the assertEqual behavior + # itself. + try: + self.assertEqual(a, b) + except self.failureException: + self.fail('assertEqual(%r, %r) failed' % (a, b)) + try: + self.assertEqual(a, b, msg='foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with msg= failed' % (a, b)) + try: + self.assertEqual(a, b, 'foo') + except self.failureException: + self.fail('assertEqual(%r, %r) with third parameter failed' % + (a, b)) + + unequal_pairs = [ + ((), []), + ({}, set()), + (set([4,1]), frozenset([4,2])), + (frozenset([4,5]), set([2,3])), + (set([3,4]), set([5,4]))] + for a, b in unequal_pairs: + self.assertRaises(self.failureException, self.assertEqual, a, b) + self.assertRaises(self.failureException, self.assertEqual, a, b, + 'foo') + self.assertRaises(self.failureException, self.assertEqual, a, b, + msg='foo') + + def testEquality(self): + self.assertListEqual([], []) + self.assertTupleEqual((), ()) + self.assertSequenceEqual([], ()) + + a = [0, 'a', []] + b = [] + self.assertRaises(unittest2.TestCase.failureException, + self.assertListEqual, a, b) + self.assertRaises(unittest2.TestCase.failureException, + self.assertListEqual, tuple(a), tuple(b)) + self.assertRaises(unittest2.TestCase.failureException, + self.assertSequenceEqual, a, tuple(b)) + + b.extend(a) + self.assertListEqual(a, b) + self.assertTupleEqual(tuple(a), tuple(b)) + self.assertSequenceEqual(a, tuple(b)) + self.assertSequenceEqual(tuple(a), b) + + self.assertRaises(self.failureException, self.assertListEqual, + a, tuple(b)) + self.assertRaises(self.failureException, self.assertTupleEqual, + tuple(a), b) + self.assertRaises(self.failureException, self.assertListEqual, None, b) + self.assertRaises(self.failureException, self.assertTupleEqual, None, + tuple(b)) + self.assertRaises(self.failureException, self.assertSequenceEqual, + None, tuple(b)) + self.assertRaises(self.failureException, self.assertListEqual, 1, 1) + self.assertRaises(self.failureException, self.assertTupleEqual, 1, 1) + self.assertRaises(self.failureException, self.assertSequenceEqual, + 1, 1) + + self.assertDictEqual({}, {}) + + c = { 'x': 1 } + d = {} + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictEqual, c, d) + + d.update(c) + self.assertDictEqual(c, d) + + d['x'] = 0 + self.assertRaises(unittest2.TestCase.failureException, + self.assertDictEqual, c, d, 'These are unequal') + + self.assertRaises(self.failureException, self.assertDictEqual, None, d) + self.assertRaises(self.failureException, self.assertDictEqual, [], d) + self.assertRaises(self.failureException, self.assertDictEqual, 1, 1) + + def testAssertItemsEqual(self): + self.assertItemsEqual([1, 2, 3], [3, 2, 1]) + self.assertItemsEqual(['foo', 'bar', 'baz'], ['bar', 'baz', 'foo']) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10], [10, 11]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10, 11], [10]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [10, 11, 10], [10, 11]) + + # Test that sequences of unhashable objects can be tested for sameness: + self.assertItemsEqual([[1, 2], [3, 4]], [[3, 4], [1, 2]]) + + self.assertItemsEqual([{'a': 1}, {'b': 2}], [{'b': 2}, {'a': 1}]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [[1]], [[2]]) + + # Test unsortable objects + self.assertItemsEqual([2j, None], [None, 2j]) + self.assertRaises(self.failureException, self.assertItemsEqual, + [2j, None], [None, 3j]) + + def testAssertSetEqual(self): + set1 = set() + set2 = set() + self.assertSetEqual(set1, set2) + + self.assertRaises(self.failureException, self.assertSetEqual, None, set2) + self.assertRaises(self.failureException, self.assertSetEqual, [], set2) + self.assertRaises(self.failureException, self.assertSetEqual, set1, None) + self.assertRaises(self.failureException, self.assertSetEqual, set1, []) + + set1 = set(['a']) + set2 = set() + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = set(['a']) + self.assertSetEqual(set1, set2) + + set1 = set(['a']) + set2 = set(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a']) + set2 = frozenset(['a', 'b']) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + set1 = set(['a', 'b']) + set2 = frozenset(['a', 'b']) + self.assertSetEqual(set1, set2) + + set1 = set() + set2 = "foo" + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + self.assertRaises(self.failureException, self.assertSetEqual, set2, set1) + + # make sure any string formatting is tuple-safe + set1 = set([(0, 1), (2, 3)]) + set2 = set([(4, 5)]) + self.assertRaises(self.failureException, self.assertSetEqual, set1, set2) + + def testInequality(self): + # Try ints + self.assertGreater(2, 1) + self.assertGreaterEqual(2, 1) + self.assertGreaterEqual(1, 1) + self.assertLess(1, 2) + self.assertLessEqual(1, 2) + self.assertLessEqual(1, 1) + self.assertRaises(self.failureException, self.assertGreater, 1, 2) + self.assertRaises(self.failureException, self.assertGreater, 1, 1) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1, 2) + self.assertRaises(self.failureException, self.assertLess, 2, 1) + self.assertRaises(self.failureException, self.assertLess, 1, 1) + self.assertRaises(self.failureException, self.assertLessEqual, 2, 1) + + # Try Floats + self.assertGreater(1.1, 1.0) + self.assertGreaterEqual(1.1, 1.0) + self.assertGreaterEqual(1.0, 1.0) + self.assertLess(1.0, 1.1) + self.assertLessEqual(1.0, 1.1) + self.assertLessEqual(1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertGreater, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertGreaterEqual, 1.0, 1.1) + self.assertRaises(self.failureException, self.assertLess, 1.1, 1.0) + self.assertRaises(self.failureException, self.assertLess, 1.0, 1.0) + self.assertRaises(self.failureException, self.assertLessEqual, 1.1, 1.0) + + # Try Strings + self.assertGreater('bug', 'ant') + self.assertGreaterEqual('bug', 'ant') + self.assertGreaterEqual('ant', 'ant') + self.assertLess('ant', 'bug') + self.assertLessEqual('ant', 'bug') + self.assertLessEqual('ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', 'ant') + + # Try Unicode + self.assertGreater(u'bug', u'ant') + self.assertGreaterEqual(u'bug', u'ant') + self.assertGreaterEqual(u'ant', u'ant') + self.assertLess(u'ant', u'bug') + self.assertLessEqual(u'ant', u'bug') + self.assertLessEqual(u'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreater, u'ant', u'bug') + self.assertRaises(self.failureException, self.assertGreater, u'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, u'ant', + u'bug') + self.assertRaises(self.failureException, self.assertLess, u'bug', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'ant', u'ant') + self.assertRaises(self.failureException, self.assertLessEqual, u'bug', u'ant') + + # Try Mixed String/Unicode + self.assertGreater('bug', u'ant') + self.assertGreater(u'bug', 'ant') + self.assertGreaterEqual('bug', u'ant') + self.assertGreaterEqual(u'bug', 'ant') + self.assertGreaterEqual('ant', u'ant') + self.assertGreaterEqual(u'ant', 'ant') + self.assertLess('ant', u'bug') + self.assertLess(u'ant', 'bug') + self.assertLessEqual('ant', u'bug') + self.assertLessEqual(u'ant', 'bug') + self.assertLessEqual('ant', u'ant') + self.assertLessEqual(u'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreater, 'ant', u'bug') + self.assertRaises(self.failureException, self.assertGreater, u'ant', 'bug') + self.assertRaises(self.failureException, self.assertGreater, 'ant', u'ant') + self.assertRaises(self.failureException, self.assertGreater, u'ant', 'ant') + self.assertRaises(self.failureException, self.assertGreaterEqual, 'ant', + u'bug') + self.assertRaises(self.failureException, self.assertGreaterEqual, u'ant', + 'bug') + self.assertRaises(self.failureException, self.assertLess, 'bug', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'bug', 'ant') + self.assertRaises(self.failureException, self.assertLess, 'ant', u'ant') + self.assertRaises(self.failureException, self.assertLess, u'ant', 'ant') + self.assertRaises(self.failureException, self.assertLessEqual, 'bug', u'ant') + self.assertRaises(self.failureException, self.assertLessEqual, u'bug', 'ant') + + def testAssertMultiLineEqual(self): + sample_text = """\ +http://www.python.org/doc/2.3/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] +""" + revised_sample_text = """\ +http://www.python.org/doc/2.4.1/lib/module-unittest.html +test case + A test case is the smallest unit of testing. [...] You may provide your + own implementation that does not subclass from TestCase, of course. +""" + sample_text_error = """\ +- http://www.python.org/doc/2.3/lib/module-unittest.html +? ^ ++ http://www.python.org/doc/2.4.1/lib/module-unittest.html +? ^^^ + test case +- A test case is the smallest unit of testing. [...] ++ A test case is the smallest unit of testing. [...] You may provide your +? +++++++++++++++++++++ ++ own implementation that does not subclass from TestCase, of course. +""" + self.maxDiff = None + for type_changer in (lambda x: x, lambda x: x.decode('utf8')): + try: + self.assertMultiLineEqual(type_changer(sample_text), + type_changer(revised_sample_text)) + except self.failureException as e: + # need to remove the first line of the error message + error = str(e).encode('utf8').split('\n', 1)[1] + + # assertMultiLineEqual is hooked up as the default for + # unicode strings - so we can't use it for this check + self.assertTrue(sample_text_error == error) + + def testAssertSequenceEqualMaxDiff(self): + self.assertEqual(self.maxDiff, 80*8) + seq1 = 'a' + 'x' * 80**2 + seq2 = 'b' + 'x' * 80**2 + diff = '\n'.join(difflib.ndiff(pprint.pformat(seq1).splitlines(), + pprint.pformat(seq2).splitlines())) + # the +1 is the leading \n added by assertSequenceEqual + omitted = unittest2.case.DIFF_OMITTED % (len(diff) + 1,) + + self.maxDiff = len(diff)//2 + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) < len(diff)) + self.assertIn(omitted, msg) + + self.maxDiff = len(diff) * 2 + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) > len(diff)) + self.assertNotIn(omitted, msg) + + self.maxDiff = None + try: + self.assertSequenceEqual(seq1, seq2) + except self.failureException as e: + msg = e.args[0] + else: + self.fail('assertSequenceEqual did not fail.') + self.assertTrue(len(msg) > len(diff)) + self.assertNotIn(omitted, msg) + + def testTruncateMessage(self): + self.maxDiff = 1 + message = self._truncateMessage('foo', 'bar') + omitted = unittest2.case.DIFF_OMITTED % len('bar') + self.assertEqual(message, 'foo' + omitted) + + self.maxDiff = None + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + self.maxDiff = 4 + message = self._truncateMessage('foo', 'bar') + self.assertEqual(message, 'foobar') + + def testAssertDictEqualTruncates(self): + test = unittest2.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertDictEqual({}, {1: 0}) + except self.failureException as e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertDictEqual did not fail') + + def testAssertMultiLineEqualTruncates(self): + test = unittest2.TestCase('assertEqual') + def truncate(msg, diff): + return 'foo' + test._truncateMessage = truncate + try: + test.assertMultiLineEqual('foo', 'bar') + except self.failureException as e: + self.assertEqual(str(e), 'foo') + else: + self.fail('assertMultiLineEqual did not fail') + + def testAssertIsNone(self): + self.assertIsNone(None) + self.assertRaises(self.failureException, self.assertIsNone, False) + self.assertIsNotNone('DjZoPloGears on Rails') + self.assertRaises(self.failureException, self.assertIsNotNone, None) + + def testAssertRegexpMatches(self): + self.assertRegexpMatches('asdfabasdf', r'ab+') + self.assertRaises(self.failureException, self.assertRegexpMatches, + 'saaas', r'aaaa') + + def testAssertRaisesRegexp(self): + class ExceptionMock(Exception): + pass + + def Stub(): + raise ExceptionMock('We expect') + + self.assertRaisesRegexp(ExceptionMock, re.compile('expect$'), Stub) + self.assertRaisesRegexp(ExceptionMock, 'expect$', Stub) + self.assertRaisesRegexp(ExceptionMock, u'expect$', Stub) + + def testAssertNotRaisesRegexp(self): + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, re.compile('x'), + lambda: None) + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, 'x', + lambda: None) + self.assertRaisesRegexp( + self.failureException, '^Exception not raised$', + self.assertRaisesRegexp, Exception, u'x', + lambda: None) + + def testAssertRaisesRegexpMismatch(self): + def Stub(): + raise Exception('Unexpected') + + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, '^Expected$', + Stub) + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, u'^Expected$', + Stub) + self.assertRaisesRegexp( + self.failureException, + r'"\^Expected\$" does not match "Unexpected"', + self.assertRaisesRegexp, Exception, + re.compile('^Expected$'), Stub) + + + def testSynonymAssertMethodNames(self): + """Test undocumented method name synonyms. + + Please do not use these methods names in your own code. + + This test confirms their continued existence and functionality + in order to avoid breaking existing code. + """ + self.assertNotEquals(3, 5) + self.assertEquals(3, 3) + self.assertAlmostEquals(2.0, 2.0) + self.assertNotAlmostEquals(3.0, 5.0) + self.assert_(True) + + def testDeepcopy(self): + # Issue: 5660 + class TestableTest(unittest2.TestCase): + def testNothing(self): + pass + + test = TestableTest('testNothing') + + # This shouldn't blow up + deepcopy(test) + + +if __name__ == "__main__": + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_discovery.py b/third_party/Python/module/unittest2/unittest2/test/test_discovery.py new file mode 100644 index 000000000000..f78c4d826b8d --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_discovery.py @@ -0,0 +1,371 @@ +import os +import re +import sys + +import unittest2 + + +class TestDiscovery(unittest2.TestCase): + + # Heavily mocked tests so I can avoid hitting the filesystem + def test_get_name_from_path(self): + loader = unittest2.TestLoader() + + loader._top_level_dir = '/foo' + name = loader._get_name_from_path('/foo/bar/baz.py') + self.assertEqual(name, 'bar.baz') + + if not __debug__: + # asserts are off + return + + self.assertRaises(AssertionError, + loader._get_name_from_path, + '/bar/baz.py') + + def test_find_tests(self): + loader = unittest2.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + path_lists = [['test1.py', 'test2.py', 'not_a_test.py', 'test_dir', + 'test.foo', 'test-not-a-module.py', 'another_dir'], + ['test3.py', 'test4.py', ]] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + def isdir(path): + return path.endswith('dir') + os.path.isdir = isdir + self.addCleanup(restore_isdir) + + def isfile(path): + # another_dir is not a package and so shouldn't be recursed into + return not path.endswith('dir') and not 'another_dir' in path + os.path.isfile = isfile + self.addCleanup(restore_isfile) + + loader._get_module_from_name = lambda path: path + ' module' + loader.loadTestsFromModule = lambda module: module + ' tests' + + top_level = os.path.abspath('/foo') + loader._top_level_dir = top_level + suite = list(loader._find_tests(top_level, 'test*.py')) + + expected = [name + ' module tests' for name in + ('test1', 'test2')] + expected.extend([('test_dir.%s' % name) + ' module tests' for name in + ('test3', 'test4')]) + self.assertEqual(suite, expected) + + def test_find_tests_with_package(self): + loader = unittest2.TestLoader() + + original_listdir = os.listdir + def restore_listdir(): + os.listdir = original_listdir + original_isfile = os.path.isfile + def restore_isfile(): + os.path.isfile = original_isfile + original_isdir = os.path.isdir + def restore_isdir(): + os.path.isdir = original_isdir + + directories = ['a_directory', 'test_directory', 'test_directory2'] + path_lists = [directories, [], [], []] + os.listdir = lambda path: path_lists.pop(0) + self.addCleanup(restore_listdir) + + os.path.isdir = lambda path: True + self.addCleanup(restore_isdir) + + os.path.isfile = lambda path: os.path.basename(path) not in directories + self.addCleanup(restore_isfile) + + class Module(object): + paths = [] + load_tests_args = [] + + def __init__(self, path): + self.path = path + self.paths.append(path) + if os.path.basename(path) == 'test_directory': + def load_tests(loader, tests, pattern): + self.load_tests_args.append((loader, tests, pattern)) + return 'load_tests' + self.load_tests = load_tests + + def __eq__(self, other): + return self.path == other.path + + # Silence py3k warning + __hash__ = None + + loader._get_module_from_name = lambda name: Module(name) + def loadTestsFromModule(module, use_load_tests): + if use_load_tests: + raise self.failureException('use_load_tests should be False for packages') + return module.path + ' module tests' + loader.loadTestsFromModule = loadTestsFromModule + + loader._top_level_dir = '/foo' + # this time no '.py' on the pattern so that it can match + # a test package + suite = list(loader._find_tests('/foo', 'test*')) + + # We should have loaded tests from the test_directory package by calling load_tests + # and directly from the test_directory2 package + self.assertEqual(suite, + ['load_tests', 'test_directory2' + ' module tests']) + self.assertEqual(Module.paths, ['test_directory', 'test_directory2']) + + # load_tests should have been called once with loader, tests and pattern + self.assertEqual(Module.load_tests_args, + [(loader, 'test_directory' + ' module tests', 'test*')]) + + def test_discover(self): + loader = unittest2.TestLoader() + + original_isfile = os.path.isfile + original_isdir = os.path.isdir + def restore_isfile(): + os.path.isfile = original_isfile + + os.path.isfile = lambda path: False + self.addCleanup(restore_isfile) + + orig_sys_path = sys.path[:] + def restore_path(): + sys.path[:] = orig_sys_path + self.addCleanup(restore_path) + + full_path = os.path.abspath(os.path.normpath('/foo')) + self.assertRaises(ImportError, + loader.discover, + '/foo/bar', top_level_dir='/foo') + + self.assertEqual(loader._top_level_dir, full_path) + self.assertIn(full_path, sys.path) + + os.path.isfile = lambda path: True + os.path.isdir = lambda path: True + + def restore_isdir(): + os.path.isdir = original_isdir + self.addCleanup(restore_isdir) + + _find_tests_args = [] + def _find_tests(start_dir, pattern): + _find_tests_args.append((start_dir, pattern)) + return ['tests'] + loader._find_tests = _find_tests + loader.suiteClass = str + + suite = loader.discover('/foo/bar/baz', 'pattern', '/foo/bar') + + top_level_dir = os.path.abspath(os.path.normpath('/foo/bar')) + start_dir = os.path.abspath(os.path.normpath('/foo/bar/baz')) + self.assertEqual(suite, "['tests']") + self.assertEqual(loader._top_level_dir, top_level_dir) + self.assertEqual(_find_tests_args, [(start_dir, 'pattern')]) + self.assertIn(top_level_dir, sys.path) + + def test_discover_with_modules_that_fail_to_import(self): + loader = unittest2.TestLoader() + + listdir = os.listdir + os.listdir = lambda _: ['test_this_does_not_exist.py'] + isfile = os.path.isfile + os.path.isfile = lambda _: True + orig_sys_path = sys.path[:] + def restore(): + os.path.isfile = isfile + os.listdir = listdir + sys.path[:] = orig_sys_path + self.addCleanup(restore) + + suite = loader.discover('.') + self.assertIn(os.getcwd(), sys.path) + self.assertEqual(suite.countTestCases(), 1) + test = list(list(suite)[0])[0] # extract test from suite + + self.assertRaises(ImportError, + lambda: test.test_this_does_not_exist()) + + def test_command_line_handling_parseArgs(self): + # Haha - take that uninstantiable class + program = object.__new__(unittest2.TestProgram) + + args = [] + def do_discovery(argv): + args.extend(argv) + program._do_discovery = do_discovery + program.parseArgs(['something', 'discover']) + self.assertEqual(args, []) + + program.parseArgs(['something', 'discover', 'foo', 'bar']) + self.assertEqual(args, ['foo', 'bar']) + + def test_command_line_handling_do_discovery_too_many_arguments(self): + class Stop(Exception): + pass + def usageExit(): + raise Stop + + program = object.__new__(unittest2.TestProgram) + program.usageExit = usageExit + + self.assertRaises(Stop, + # too many args + lambda: program._do_discovery(['one', 'two', 'three', 'four'])) + + + def test_command_line_handling_do_discovery_calls_loader(self): + program = object.__new__(unittest2.TestProgram) + + class Loader(object): + args = [] + def discover(self, start_dir, pattern, top_level_dir): + self.args.append((start_dir, pattern, top_level_dir)) + return 'tests' + + program._do_discovery(['-v'], Loader=Loader) + self.assertEqual(program.verbosity, 2) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['--verbose'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery([], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish', 'eggs'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['fish', 'eggs', 'ham'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', 'ham')]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-s', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'test*.py', None)]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-t', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'test*.py', 'fish')]) + + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(['-p', 'fish'], Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('.', 'fish', None)]) + self.assertFalse(program.failfast) + self.assertFalse(program.catchbreak) + + args = ['-p', 'eggs', '-s', 'fish', '-v', '-f'] + try: + import signal + except ImportError: + signal = None + else: + args.append('-c') + Loader.args = [] + program = object.__new__(unittest2.TestProgram) + program._do_discovery(args, Loader=Loader) + self.assertEqual(program.test, 'tests') + self.assertEqual(Loader.args, [('fish', 'eggs', None)]) + self.assertEqual(program.verbosity, 2) + self.assertTrue(program.failfast) + if signal is not None: + self.assertTrue(program.catchbreak) + + def test_detect_module_clash(self): + class Module(object): + __file__ = 'bar/foo.py' + sys.modules['foo'] = Module + full_path = os.path.abspath('foo') + original_listdir = os.listdir + original_isfile = os.path.isfile + original_isdir = os.path.isdir + + def cleanup(): + os.listdir = original_listdir + os.path.isfile = original_isfile + os.path.isdir = original_isdir + del sys.modules['foo'] + if full_path in sys.path: + sys.path.remove(full_path) + self.addCleanup(cleanup) + + def listdir(_): + return ['foo.py'] + def isfile(_): + return True + def isdir(_): + return True + os.listdir = listdir + os.path.isfile = isfile + os.path.isdir = isdir + + loader = unittest2.TestLoader() + + mod_dir = os.path.abspath('bar') + expected_dir = os.path.abspath('foo') + msg = re.escape(r"'foo' module incorrectly imported from %r. Expected %r. " + "Is this module globally installed?" % (mod_dir, expected_dir)) + self.assertRaisesRegexp( + ImportError, '^%s$' % msg, loader.discover, + start_dir='foo', pattern='foo.py' + ) + self.assertEqual(sys.path[0], full_path) + + + def test_discovery_from_dotted_path(self): + loader = unittest2.TestLoader() + + tests = [self] + expectedPath = os.path.abspath(os.path.dirname(unittest2.test.__file__)) + + self.wasRun = False + def _find_tests(start_dir, pattern): + self.wasRun = True + self.assertEqual(start_dir, expectedPath) + return tests + loader._find_tests = _find_tests + suite = loader.discover('unittest2.test') + self.assertTrue(self.wasRun) + self.assertEqual(suite._tests, tests) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_functiontestcase.py b/third_party/Python/module/unittest2/unittest2/test/test_functiontestcase.py new file mode 100644 index 000000000000..263aed5a9bf6 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_functiontestcase.py @@ -0,0 +1,150 @@ +import unittest2 +import six + +from unittest2.test.support import LoggingResult + + +class Test_FunctionTestCase(unittest2.TestCase): + + # "Return the number of tests represented by the this test object. For + # unittest2.TestCase instances, this will always be 1" + def test_countTestCases(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertEqual(test.countTestCases(), 1) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if setUp() raises + # an exception. + def test_run_call_order__error_in_setUp(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + raise RuntimeError('raised by setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'addError', 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test raises + # an error (as opposed to a failure). + def test_run_call_order__error_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + raise RuntimeError('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'addError', 'tearDown', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if the test signals + # a failure (as opposed to an error). + def test_run_call_order__failure_in_test(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + self.fail('raised by test') + + def tearDown(): + events.append('tearDown') + + expected = ['startTest', 'setUp', 'test', 'addFailure', 'tearDown', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "When a setUp() method is defined, the test runner will run that method + # prior to each test. Likewise, if a tearDown() method is defined, the + # test runner will invoke that method after each test. In the example, + # setUp() was used to create a fresh sequence for each test." + # + # Make sure the proper call order is maintained, even if tearDown() raises + # an exception. + def test_run_call_order__error_in_tearDown(self): + events = [] + result = LoggingResult(events) + + def setUp(): + events.append('setUp') + + def test(): + events.append('test') + + def tearDown(): + events.append('tearDown') + raise RuntimeError('raised by tearDown') + + expected = ['startTest', 'setUp', 'test', 'tearDown', 'addError', + 'stopTest'] + unittest2.FunctionTestCase(test, setUp, tearDown).run(result) + self.assertEqual(events, expected) + + # "Return a string identifying the specific test case." + # + # Because of the vague nature of the docs, I'm not going to lock this + # test down too much. Really all that can be asserted is that the id() + # will be a string (either 8-byte or unicode -- again, because the docs + # just say "string") + def test_id(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertIsInstance(test.id(), six.string_types) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__no_docstring(self): + test = unittest2.FunctionTestCase(lambda: None) + + self.assertEqual(test.shortDescription(), None) + + # "Returns a one-line description of the test, or None if no description + # has been provided. The default implementation of this method returns + # the first line of the test method's docstring, if available, or None." + def test_shortDescription__singleline_docstring(self): + desc = "this tests foo" + test = unittest2.FunctionTestCase(lambda: None, description=desc) + + self.assertEqual(test.shortDescription(), "this tests foo") + + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_loader.py b/third_party/Python/module/unittest2/unittest2/test/test_loader.py new file mode 100644 index 000000000000..3a61f33e3191 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_loader.py @@ -0,0 +1,1271 @@ +import sys +import types + +import unittest2 + + +class Test_TestLoader(unittest2.TestCase): + + ### Tests for TestLoader.loadTestsFromTestCase + ################################################################ + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + def test_loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure it does the right thing even if no tests were found + def test_loadTestsFromTestCase__no_matches(self): + class Foo(unittest2.TestCase): + def foo_bar(self): pass + + empty_suite = unittest2.TestSuite() + + loader = unittest2.TestLoader() + self.assertEqual(loader.loadTestsFromTestCase(Foo), empty_suite) + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # What happens if loadTestsFromTestCase() is given an object + # that isn't a subclass of TestCase? Specifically, what happens + # if testCaseClass is a subclass of TestSuite? + # + # This is checked for specifically in the code, so we better add a + # test for it. + def test_loadTestsFromTestCase__TestSuite_subclass(self): + class NotATestCase(unittest2.TestSuite): + pass + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromTestCase(NotATestCase) + except TypeError: + pass + else: + self.fail('Should raise TypeError') + + # "Return a suite of all tests cases contained in the TestCase-derived + # class testCaseClass" + # + # Make sure loadTestsFromTestCase() picks up the default test method + # name (as specified by TestCase), even though the method name does + # not match the default TestLoader.testMethodPrefix string + def test_loadTestsFromTestCase__default_method_name(self): + class Foo(unittest2.TestCase): + def runTest(self): + pass + + loader = unittest2.TestLoader() + # This has to be false for the test to succeed + self.assertFalse('runTest'.startswith(loader.testMethodPrefix)) + + suite = loader.loadTestsFromTestCase(Foo) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [Foo('runTest')]) + + ################################################################ + ### /Tests for TestLoader.loadTestsFromTestCase + + ### Tests for TestLoader.loadTestsFromModule + ################################################################ + + # "This method searches `module` for classes derived from TestCase" + def test_loadTestsFromModule__TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = [loader.suiteClass([MyTestCase('test')])] + self.assertEqual(list(suite), expected) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (no TestCase instances)? + def test_loadTestsFromModule__no_TestCase_instances(self): + m = types.ModuleType('m') + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "This method searches `module` for classes derived from TestCase" + # + # What happens if no tests are found (TestCases instances, but no tests)? + def test_loadTestsFromModule__no_TestCase_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [loader.suiteClass()]) + + # "This method searches `module` for classes derived from TestCase"s + # + # What happens if loadTestsFromModule() is given something other + # than a module? + # + # XXX Currently, it succeeds anyway. This flexibility + # should either be documented or loadTestsFromModule() should + # raise a TypeError + # + # XXX Certain people are using this behaviour. We'll add a test for it + def test_loadTestsFromModule__not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(NotAModule) + + reference = [unittest2.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + + # Check that loadTestsFromModule honors (or not) a module + # with a load_tests function. + def test_loadTestsFromModule__load_tests(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + load_tests_args = [] + def load_tests(loader, tests, pattern): + self.assertIsInstance(tests, unittest2.TestSuite) + load_tests_args.extend((loader, tests, pattern)) + return tests + m.load_tests = load_tests + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest2.TestSuite) + self.assertEquals(load_tests_args, [loader, suite, None]) + + load_tests_args = [] + suite = loader.loadTestsFromModule(m, use_load_tests=False) + self.assertEquals(load_tests_args, []) + + def test_loadTestsFromModule__faulty_load_tests(self): + m = types.ModuleType('m') + + def load_tests(loader, tests, pattern): + raise TypeError('some failure') + m.load_tests = load_tests + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromModule(m) + self.assertIsInstance(suite, unittest2.TestSuite) + self.assertEqual(suite.countTestCases(), 1) + test = list(suite)[0] + + self.assertRaisesRegexp(TypeError, "some failure", test.m) + + + ################################################################ + ### /Tests for TestLoader.loadTestsFromModule() + + ### Tests for TestLoader.loadTestsFromName() + ################################################################ + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromName__empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('') + except ValueError as e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the name contains invalid characters? + def test_loadTestsFromName__malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise ValueError or ImportError? + try: + loader.loadTestsFromName('abc () //') + except ValueError: + pass + except ImportError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve ... to a + # module" + # + # What happens when a module by that name can't be found? + def test_loadTestsFromName__unknown_module_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('sdasfasfasdf') + except ImportError as e: + self.assertEqual(str(e), "No module named sdasfasfasdf") + else: + self.fail("TestLoader.loadTestsFromName failed to raise ImportError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module is found, but the attribute can't? + def test_loadTestsFromName__unknown_attr_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('unittest2.sdasfasfasdf') + except AttributeError as e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when we provide the module, but the attribute can't be + # found? + def test_loadTestsFromName__relative_unknown_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('sdasfasfasdf', unittest2) + except AttributeError as e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise ValueError when passed an empty + # name relative to a provided module? + # + # XXX Should probably raise a ValueError instead of an AttributeError + def test_loadTestsFromName__relative_empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromName('', unittest2) + except AttributeError: + pass + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when an impossible name is given, relative to the provided + # `module`? + def test_loadTestsFromName__relative_malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + try: + loader.loadTestsFromName('abc () //', unittest2) + except ValueError: + pass + except AttributeError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise ValueError") + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromName raise TypeError when the `module` argument + # isn't a module object? + # + # XXX Accepts the not-a-module object, ignorning the object's type + # This should raise an exception or the method name should be changed + # + # XXX Some people are relying on this, so keep it for now + def test_loadTestsFromName__relative_not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('test_2', NotAModule) + + reference = [MyTestCase('test')] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromName__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('testcase_1', m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may + # resolve either to ... a test case class" + def test_loadTestsFromName__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testcase_1', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + def test_loadTestsFromName__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testsuite = unittest2.TestSuite([MyTestCase('test')]) + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testsuite', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + def test_loadTestsFromName__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does loadTestsFromName() raise the proper exception when trying to + # resolve "a test method within a test case class" that doesn't exist + # for the given name (relative to a provided module)? + def test_loadTestsFromName__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('testcase_1.testfoo', m) + except AttributeError as e: + self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromName__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + testcase_2 = unittest2.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest2.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('return_TestSuite', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1, testcase_2]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromName__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__callable__TestCase_instance_ProperSuiteClass(self): + class SubTestSuite(unittest2.TestSuite): + pass + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + loader.suiteClass = SubTestSuite + suite = loader.loadTestsFromName('return_TestCase', m) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [testcase_1]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test method within a test case class" + #***************************************************************** + #Override the suiteClass attribute to ensure that the suiteClass + #attribute is used + def test_loadTestsFromName__relative_testmethod_ProperSuiteClass(self): + class SubTestSuite(unittest2.TestSuite): + pass + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + loader.suiteClass=SubTestSuite + suite = loader.loadTestsFromName('testcase_1.test', m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [MyTestCase('test')]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens if the callable returns something else? + def test_loadTestsFromName__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromName('return_wrong', m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromName failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromName__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest2.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest2.TestLoader() + try: + suite = loader.loadTestsFromName(module_name) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### Tests for TestLoader.loadTestsFromName() + + ### Tests for TestLoader.loadTestsFromNames() + ################################################################ + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # + # What happens if that sequence of names is empty? + def test_loadTestsFromNames__empty_name_list(self): + loader = unittest2.TestLoader() + + suite = loader.loadTestsFromNames([]) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "Similar to loadTestsFromName(), but takes a sequence of names rather + # than a single name." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens if that sequence of names is empty? + # + # XXX Should this raise a ValueError or just return an empty TestSuite? + def test_loadTestsFromNames__relative_empty_name_list(self): + loader = unittest2.TestLoader() + + suite = loader.loadTestsFromNames([], unittest2) + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), []) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Is ValueError raised in response to an empty name? + def test_loadTestsFromNames__empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['']) + except ValueError as e: + self.assertEqual(str(e), "Empty module name") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when presented with an impossible module name? + def test_loadTestsFromNames__malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise ValueError or ImportError? + try: + loader.loadTestsFromNames(['abc () //']) + except ValueError: + pass + except ImportError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when no module can be found for the given name? + def test_loadTestsFromNames__unknown_module_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['sdasfasfasdf']) + except ImportError as e: + self.assertEqual(str(e), "No module named sdasfasfasdf") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ImportError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # What happens when the module can be found, but not the attribute? + def test_loadTestsFromNames__unknown_attr_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['unittest2.sdasfasfasdf', 'unittest2']) + except AttributeError as e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromNames failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when given an unknown attribute on a specified `module` + # argument? + def test_loadTestsFromNames__unknown_name_relative_1(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['sdasfasfasdf'], unittest2) + except AttributeError as e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # Do unknown attributes (relative to a provided module) still raise an + # exception even in the presence of valid attribute names? + def test_loadTestsFromNames__unknown_name_relative_2(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames(['TestCase', 'sdasfasfasdf'], unittest2) + except AttributeError as e: + self.assertEqual(str(e), "'module' object has no attribute 'sdasfasfasdf'") + else: + self.fail("TestLoader.loadTestsFromName failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when faced with the empty string? + # + # XXX This currently raises AttributeError, though ValueError is probably + # more appropriate + def test_loadTestsFromNames__relative_empty_name(self): + loader = unittest2.TestLoader() + + try: + loader.loadTestsFromNames([''], unittest2) + except AttributeError: + pass + else: + self.fail("Failed to raise ValueError") + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # ... + # "The method optionally resolves name relative to the given module" + # + # What happens when presented with an impossible attribute name? + def test_loadTestsFromNames__relative_malformed_name(self): + loader = unittest2.TestLoader() + + # XXX Should this raise AttributeError or ValueError? + try: + loader.loadTestsFromNames(['abc () //'], unittest2) + except AttributeError: + pass + except ValueError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise ValueError") + + # "The method optionally resolves name relative to the given module" + # + # Does loadTestsFromNames() make sure the provided `module` is in fact + # a module? + # + # XXX This validation is currently not done. This flexibility should + # either be documented or a TypeError should be raised. + def test_loadTestsFromNames__relative_not_a_module(self): + class MyTestCase(unittest2.TestCase): + def test(self): + pass + + class NotAModule(object): + test_2 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['test_2'], NotAModule) + + reference = [unittest2.TestSuite([MyTestCase('test')])] + self.assertEqual(list(suite), reference) + + # "The specifier name is a ``dotted name'' that may resolve either to + # a module, a test case class, a TestSuite instance, a test method + # within a test case class, or a callable object which returns a + # TestCase or TestSuite instance." + # + # Does it raise an exception if the name resolves to an invalid + # object? + def test_loadTestsFromNames__relative_bad_object(self): + m = types.ModuleType('m') + m.testcase_1 = object() + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['testcase_1'], m) + except TypeError: + pass + else: + self.fail("Should have raised TypeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a test case class" + def test_loadTestsFromNames__relative_TestCase_subclass(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = loader.suiteClass([MyTestCase('test')]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a TestSuite instance" + def test_loadTestsFromNames__relative_TestSuite(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testsuite = unittest2.TestSuite([MyTestCase('test')]) + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testsuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + self.assertEqual(list(suite), [m.testsuite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + def test_loadTestsFromNames__relative_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['testcase_1.test'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([MyTestCase('test')]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to ... a + # test method within a test case class" + # + # Does the method gracefully handle names that initially look like they + # resolve to "a test method within a test case class" but don't? + def test_loadTestsFromNames__relative_invalid_testmethod(self): + m = types.ModuleType('m') + class MyTestCase(unittest2.TestCase): + def test(self): + pass + m.testcase_1 = MyTestCase + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['testcase_1.testfoo'], m) + except AttributeError as e: + self.assertEqual(str(e), "type object 'MyTestCase' has no attribute 'testfoo'") + else: + self.fail("Failed to raise AttributeError") + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a ... TestSuite instance" + def test_loadTestsFromNames__callable__TestSuite(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + testcase_2 = unittest2.FunctionTestCase(lambda: None) + def return_TestSuite(): + return unittest2.TestSuite([testcase_1, testcase_2]) + m.return_TestSuite = return_TestSuite + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['return_TestSuite'], m) + self.assertIsInstance(suite, loader.suiteClass) + + expected = unittest2.TestSuite([testcase_1, testcase_2]) + self.assertEqual(list(suite), [expected]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase ... instance" + def test_loadTestsFromNames__callable__TestCase_instance(self): + m = types.ModuleType('m') + testcase_1 = unittest2.FunctionTestCase(lambda: None) + def return_TestCase(): + return testcase_1 + m.return_TestCase = return_TestCase + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['return_TestCase'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # Are staticmethods handled correctly? + def test_loadTestsFromNames__callable__call_staticmethod(self): + m = types.ModuleType('m') + class Test1(unittest2.TestCase): + def test(self): + pass + + testcase_1 = Test1('test') + class Foo(unittest2.TestCase): + @staticmethod + def foo(): + return testcase_1 + m.Foo = Foo + + loader = unittest2.TestLoader() + suite = loader.loadTestsFromNames(['Foo.foo'], m) + self.assertIsInstance(suite, loader.suiteClass) + + ref_suite = unittest2.TestSuite([testcase_1]) + self.assertEqual(list(suite), [ref_suite]) + + # "The specifier name is a ``dotted name'' that may resolve ... to + # ... a callable object which returns a TestCase or TestSuite instance" + # + # What happens when the callable returns something else? + def test_loadTestsFromNames__callable__wrong_type(self): + m = types.ModuleType('m') + def return_wrong(): + return 6 + m.return_wrong = return_wrong + + loader = unittest2.TestLoader() + try: + loader.loadTestsFromNames(['return_wrong'], m) + except TypeError: + pass + else: + self.fail("TestLoader.loadTestsFromNames failed to raise TypeError") + + # "The specifier can refer to modules and packages which have not been + # imported; they will be imported as a side-effect" + def test_loadTestsFromNames__module_not_loaded(self): + # We're going to try to load this module as a side-effect, so it + # better not be loaded before we try. + # + module_name = 'unittest2.test.dummy' + sys.modules.pop(module_name, None) + + loader = unittest2.TestLoader() + try: + suite = loader.loadTestsFromNames([module_name]) + + self.assertIsInstance(suite, loader.suiteClass) + self.assertEqual(list(suite), [unittest2.TestSuite()]) + + # module should now be loaded, thanks to loadTestsFromName() + self.assertIn(module_name, sys.modules) + finally: + if module_name in sys.modules: + del sys.modules[module_name] + + ################################################################ + ### /Tests for TestLoader.loadTestsFromNames() + + ### Tests for TestLoader.getTestCaseNames() + ################################################################ + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Test.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames(self): + class Test(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + loader = unittest2.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), ['test_1', 'test_2']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Does getTestCaseNames() behave appropriately if no tests are found? + def test_getTestCaseNames__no_tests(self): + class Test(unittest2.TestCase): + def foobar(self): pass + + loader = unittest2.TestLoader() + + self.assertEqual(loader.getTestCaseNames(Test), []) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Are not-TestCases handled gracefully? + # + # XXX This should raise a TypeError, not return a list + # + # XXX It's too late in the 2.5 release cycle to fix this, but it should + # probably be revisited for 2.6 + def test_getTestCaseNames__not_a_TestCase(self): + class BadCase(int): + def test_foo(self): + pass + + loader = unittest2.TestLoader() + names = loader.getTestCaseNames(BadCase) + + self.assertEqual(names, ['test_foo']) + + # "Return a sorted sequence of method names found within testCaseClass" + # + # Make sure inherited names are handled. + # + # TestP.foobar is defined to make sure getTestCaseNames() respects + # loader.testMethodPrefix + def test_getTestCaseNames__inheritance(self): + class TestP(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foobar(self): pass + + class TestC(TestP): + def test_1(self): pass + def test_3(self): pass + + loader = unittest2.TestLoader() + + names = ['test_1', 'test_2', 'test_3'] + self.assertEqual(loader.getTestCaseNames(TestC), names) + + ################################################################ + ### /Tests for TestLoader.getTestCaseNames() + + ### Tests for TestLoader.testMethodPrefix + ################################################################ + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests_1 = unittest2.TestSuite([Foo('foo_bar')]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = [unittest2.TestSuite([Foo('foo_bar')])] + tests_2 = [unittest2.TestSuite([Foo('test_1'), Foo('test_2')])] + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(list(loader.loadTestsFromModule(m)), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest2.TestSuite([Foo('foo_bar')]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromName('Foo', m), tests_2) + + # "String giving the prefix of method names which will be interpreted as + # test methods" + # + # Implicit in the documentation is that testMethodPrefix is respected by + # all loadTestsFrom* methods. + def test_testMethodPrefix__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests_1 = unittest2.TestSuite([unittest2.TestSuite([Foo('foo_bar')])]) + tests_2 = unittest2.TestSuite([Foo('test_1'), Foo('test_2')]) + tests_2 = unittest2.TestSuite([tests_2]) + + loader = unittest2.TestLoader() + loader.testMethodPrefix = 'foo' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_1) + + loader.testMethodPrefix = 'test' + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests_2) + + # "The default value is 'test'" + def test_testMethodPrefix__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.testMethodPrefix == 'test') + + ################################################################ + ### /Tests for TestLoader.testMethodPrefix + + ### Tests for TestLoader.sortTestMethodsUsing + ################################################################ + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = unittest2.reversed_cmp_ + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = unittest2.reversed_cmp_ + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromModule(m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = unittest2.reversed_cmp_ + + tests = loader.suiteClass([Foo('test_2'), Foo('test_1')]) + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames() and all the loadTestsFromX() methods" + def test_sortTestMethodsUsing__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + m.Foo = Foo + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = unittest2.reversed_cmp_ + + tests = [loader.suiteClass([Foo('test_2'), Foo('test_1')])] + self.assertEqual(list(loader.loadTestsFromNames(['Foo'], m)), tests) + + # "Function to be used to compare method names when sorting them in + # getTestCaseNames()" + # + # Does it actually affect getTestCaseNames()? + def test_sortTestMethodsUsing__getTestCaseNames(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = unittest2.reversed_cmp_ + + test_names = ['test_2', 'test_1'] + self.assertEqual(loader.getTestCaseNames(Foo), test_names) + + # "The default value is the built-in cmp() function" + def test_sortTestMethodsUsing__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.sortTestMethodsUsing is unittest2.cmp_) + + # "it can be set to None to disable the sort." + # + # XXX How is this different from reassigning cmp? Are the tests returned + # in a random order or something? This behaviour should die + def test_sortTestMethodsUsing__None(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + loader = unittest2.TestLoader() + loader.sortTestMethodsUsing = None + + test_names = ['test_2', 'test_1'] + self.assertEqual(set(loader.getTestCaseNames(Foo)), set(test_names)) + + ################################################################ + ### /Tests for TestLoader.sortTestMethodsUsing + + ### Tests for TestLoader.suiteClass + ################################################################ + + # "Callable object that constructs a test suite from a list of tests." + def test_suiteClass__loadTestsFromTestCase(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromTestCase(Foo), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromModule(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromModule(m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromName(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [Foo('test_1'), Foo('test_2')] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromName('Foo', m), tests) + + # It is implicit in the documentation for TestLoader.suiteClass that + # all TestLoader.loadTestsFrom* methods respect it. Let's make sure + def test_suiteClass__loadTestsFromNames(self): + m = types.ModuleType('m') + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def foo_bar(self): pass + m.Foo = Foo + + tests = [[Foo('test_1'), Foo('test_2')]] + + loader = unittest2.TestLoader() + loader.suiteClass = list + self.assertEqual(loader.loadTestsFromNames(['Foo'], m), tests) + + # "The default value is the TestSuite class" + def test_suiteClass__default_value(self): + loader = unittest2.TestLoader() + self.assertTrue(loader.suiteClass is unittest2.TestSuite) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_new_tests.py b/third_party/Python/module/unittest2/unittest2/test/test_new_tests.py new file mode 100644 index 000000000000..cc96bcb6a058 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_new_tests.py @@ -0,0 +1,46 @@ +from cStringIO import StringIO + +import unittest +import unittest2 + +from unittest2.test.support import resultFactory + + +class TestUnittest(unittest2.TestCase): + + def assertIsSubclass(self, actual, klass): + self.assertTrue(issubclass(actual, klass), "Not a subclass.") + + def testInheritance(self): + self.assertIsSubclass(unittest2.TestCase, unittest.TestCase) + self.assertIsSubclass(unittest2.TestResult, unittest.TestResult) + self.assertIsSubclass(unittest2.TestSuite, unittest.TestSuite) + self.assertIsSubclass(unittest2.TextTestRunner, unittest.TextTestRunner) + self.assertIsSubclass(unittest2.TestLoader, unittest.TestLoader) + self.assertIsSubclass(unittest2.TextTestResult, unittest.TestResult) + + def test_new_runner_old_case(self): + runner = unittest2.TextTestRunner(resultclass=resultFactory, + stream=StringIO()) + class Test(unittest.TestCase): + def testOne(self): + pass + suite = unittest2.TestSuite((Test('testOne'),)) + result = runner.run(suite) + self.assertEqual(result.testsRun, 1) + self.assertEqual(len(result.errors), 0) + + def test_old_runner_new_case(self): + runner = unittest.TextTestRunner(stream=StringIO()) + class Test(unittest2.TestCase): + def testOne(self): + self.assertDictEqual({}, {}) + + suite = unittest.TestSuite((Test('testOne'),)) + result = runner.run(suite) + self.assertEqual(result.testsRun, 1) + self.assertEqual(len(result.errors), 0) + + +if __name__ == '__main__': + unittest2.main()
\ No newline at end of file diff --git a/third_party/Python/module/unittest2/unittest2/test/test_program.py b/third_party/Python/module/unittest2/unittest2/test/test_program.py new file mode 100644 index 000000000000..56077a6bb3a7 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_program.py @@ -0,0 +1,239 @@ +from cStringIO import StringIO + +import sys +import unittest2 + +hasInstallHandler = hasattr(unittest2, 'installHandler') + +class Test_TestProgram(unittest2.TestCase): + + # Horrible white box test + def testNoExit(self): + result = object() + test = object() + + class FakeRunner(object): + def run(self, test): + self.test = test + return result + + runner = FakeRunner() + + oldParseArgs = unittest2.TestProgram.parseArgs + def restoreParseArgs(): + unittest2.TestProgram.parseArgs = oldParseArgs + unittest2.TestProgram.parseArgs = lambda *args: None + self.addCleanup(restoreParseArgs) + + def removeTest(): + del unittest2.TestProgram.test + unittest2.TestProgram.test = test + self.addCleanup(removeTest) + + program = unittest2.TestProgram(testRunner=runner, exit=False, verbosity=2) + + self.assertEqual(program.result, result) + self.assertEqual(runner.test, test) + self.assertEqual(program.verbosity, 2) + + class FooBar(unittest2.TestCase): + def testPass(self): + assert True + def testFail(self): + assert False + + class FooBarLoader(unittest2.TestLoader): + """Test loader that returns a suite containing FooBar.""" + def loadTestsFromModule(self, module): + return self.suiteClass( + [self.loadTestsFromTestCase(Test_TestProgram.FooBar)]) + + + def test_NonExit(self): + program = unittest2.main(exit=False, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + testLoader=self.FooBarLoader()) + self.assertTrue(hasattr(program, 'result')) + + + def test_Exit(self): + self.assertRaises( + SystemExit, + unittest2.main, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + exit=True, + testLoader=self.FooBarLoader()) + + + def test_ExitAsDefault(self): + self.assertRaises( + SystemExit, + unittest2.main, + argv=["foobar"], + testRunner=unittest2.TextTestRunner(stream=StringIO()), + testLoader=self.FooBarLoader()) + + +class InitialisableProgram(unittest2.TestProgram): + exit = False + result = None + verbosity = 1 + defaultTest = None + testRunner = None + testLoader = unittest2.defaultTestLoader + progName = 'test' + test = 'test' + def __init__(self, *args): + pass + +RESULT = object() + +class FakeRunner(object): + initArgs = None + test = None + raiseError = False + + def __init__(self, **kwargs): + FakeRunner.initArgs = kwargs + if FakeRunner.raiseError: + FakeRunner.raiseError = False + raise TypeError + + def run(self, test): + FakeRunner.test = test + return RESULT + +class TestCommandLineArgs(unittest2.TestCase): + + def setUp(self): + self.program = InitialisableProgram() + self.program.createTests = lambda: None + FakeRunner.initArgs = None + FakeRunner.test = None + FakeRunner.raiseError = False + + def testHelpAndUnknown(self): + program = self.program + def usageExit(msg=None): + program.msg = msg + program.exit = True + program.usageExit = usageExit + + for opt in '-h', '-H', '--help': + program.exit = False + program.parseArgs([None, opt]) + self.assertTrue(program.exit) + self.assertIsNone(program.msg) + + program.parseArgs([None, '-$']) + self.assertTrue(program.exit) + self.assertIsNotNone(program.msg) + + def testVerbosity(self): + program = self.program + + for opt in '-q', '--quiet': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 0) + + for opt in '-v', '--verbose': + program.verbosity = 1 + program.parseArgs([None, opt]) + self.assertEqual(program.verbosity, 2) + + def testBufferCatchFailfast(self): + program = self.program + for arg, attr in (('buffer', 'buffer'), ('failfast', 'failfast'), + ('catch', 'catchbreak')): + if attr == 'catch' and not hasInstallHandler: + continue + + short_opt = '-%s' % arg[0] + long_opt = '--%s' % arg + for opt in short_opt, long_opt: + setattr(program, attr, None) + + program.parseArgs([None, opt]) + self.assertTrue(getattr(program, attr)) + + for opt in short_opt, long_opt: + not_none = object() + setattr(program, attr, not_none) + + program.parseArgs([None, opt]) + self.assertEqual(getattr(program, attr), not_none) + + def testRunTestsRunnerClass(self): + program = self.program + + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + + program.runTests() + + self.assertEqual(FakeRunner.initArgs, {'verbosity': 'verbosity', + 'failfast': 'failfast', + 'buffer': 'buffer'}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testRunTestsRunnerInstance(self): + program = self.program + + program.testRunner = FakeRunner() + FakeRunner.initArgs = None + + program.runTests() + + # A new FakeRunner should not have been instantiated + self.assertIsNone(FakeRunner.initArgs) + + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testRunTestsOldRunnerClass(self): + program = self.program + + FakeRunner.raiseError = True + program.testRunner = FakeRunner + program.verbosity = 'verbosity' + program.failfast = 'failfast' + program.buffer = 'buffer' + program.test = 'test' + + program.runTests() + + # If initialising raises a type error it should be retried + # without the new keyword arguments + self.assertEqual(FakeRunner.initArgs, {}) + self.assertEqual(FakeRunner.test, 'test') + self.assertIs(program.result, RESULT) + + def testCatchBreakInstallsHandler(self): + module = sys.modules['unittest2.main'] + original = module.installHandler + def restore(): + module.installHandler = original + self.addCleanup(restore) + + self.installed = False + def fakeInstallHandler(): + self.installed = True + module.installHandler = fakeInstallHandler + + program = self.program + program.catchbreak = True + + program.testRunner = FakeRunner + + program.runTests() + self.assertTrue(self.installed) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_result.py b/third_party/Python/module/unittest2/unittest2/test/test_result.py new file mode 100644 index 000000000000..69b31d542eae --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_result.py @@ -0,0 +1,418 @@ +from __future__ import print_function + +import sys +import textwrap +from StringIO import StringIO + +import unittest2 + + +class Test_TestResult(unittest2.TestCase): + # Note: there are not separate tests for TestResult.wasSuccessful(), + # TestResult.errors, TestResult.failures, TestResult.testsRun or + # TestResult.shouldStop because these only have meaning in terms of + # other TestResult methods. + # + # Accordingly, tests for the aforenamed attributes are incorporated + # in with the tests for the defining methods. + ################################################################ + + def test_init(self): + result = unittest2.TestResult() + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 0) + self.assertEqual(result.shouldStop, False) + self.assertIsNone(result._stdout_buffer) + self.assertIsNone(result._stderr_buffer) + + # "This method can be called to signal that the set of tests being + # run should be aborted by setting the TestResult's shouldStop + # attribute to True." + def test_stop(self): + result = unittest2.TestResult() + + result.stop() + + self.assertEqual(result.shouldStop, True) + + # "Called when the test case test is about to be run. The default + # implementation simply increments the instance's testsRun counter." + def test_startTest(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # "Called after the test case test has been executed, regardless of + # the outcome. The default implementation does nothing." + def test_stopTest(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + result.stopTest(test) + + # Same tests as above; make sure nothing has changed + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "Called before and after tests are run. The default implementation does nothing." + def test_startTestRun_stopTestRun(self): + result = unittest2.TestResult() + result.startTestRun() + result.stopTestRun() + + # "addSuccess(test)" + # ... + # "Called when the test case test succeeds" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addSuccess(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + + result = unittest2.TestResult() + + result.startTest(test) + result.addSuccess(test) + result.stopTest(test) + + self.assertTrue(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + # "addFailure(test, err)" + # ... + # "Called when the test case test signals a failure. err is a tuple of + # the form returned by sys.exc_info(): (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addFailure(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + test.fail("foo") + except: + exc_info_tuple = sys.exc_info() + + result = unittest2.TestResult() + + result.startTest(test) + result.addFailure(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.failures), 1) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.failures[0] + self.assertTrue(test_case is test) + self.assertIsInstance(formatted_exc, str) + + # "addError(test, err)" + # ... + # "Called when the test case test raises an unexpected exception err + # is a tuple of the form returned by sys.exc_info(): + # (type, value, traceback)" + # ... + # "wasSuccessful() - Returns True if all tests run so far have passed, + # otherwise returns False" + # ... + # "testsRun - The total number of tests run so far." + # ... + # "errors - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test which raised an + # unexpected exception. Contains formatted + # tracebacks instead of sys.exc_info() results." + # ... + # "failures - A list containing 2-tuples of TestCase instances and + # formatted tracebacks. Each tuple represents a test where a failure was + # explicitly signalled using the TestCase.fail*() or TestCase.assert*() + # methods. Contains formatted tracebacks instead + # of sys.exc_info() results." + def test_addError(self): + class Foo(unittest2.TestCase): + def test_1(self): + pass + + test = Foo('test_1') + try: + raise TypeError() + except: + exc_info_tuple = sys.exc_info() + + result = unittest2.TestResult() + + result.startTest(test) + result.addError(test, exc_info_tuple) + result.stopTest(test) + + self.assertFalse(result.wasSuccessful()) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 1) + self.assertEqual(result.shouldStop, False) + + test_case, formatted_exc = result.errors[0] + self.assertTrue(test_case is test) + self.assertIsInstance(formatted_exc, str) + + def testGetDescriptionWithoutDocstring(self): + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + 'testGetDescriptionWithoutDocstring (' + __name__ + + '.Test_TestResult)') + + def testGetDescriptionWithOneLineDocstring(self): + """Tests getDescription() for a method with a docstring.""" + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithOneLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a docstring.')) + + def testGetDescriptionWithMultiLineDocstring(self): + """Tests getDescription() for a method with a longer docstring. + The second line of the docstring. + """ + result = unittest2.TextTestResult(None, True, 1) + self.assertEqual( + result.getDescription(self), + ('testGetDescriptionWithMultiLineDocstring ' + '(' + __name__ + '.Test_TestResult)\n' + 'Tests getDescription() for a method with a longer ' + 'docstring.')) + + def testStackFrameTrimming(self): + class Frame(object): + class tb_frame(object): + f_globals = {} + result = unittest2.TestResult() + self.assertFalse(result._is_relevant_tb_level(Frame)) + + Frame.tb_frame.f_globals['__unittest'] = True + self.assertTrue(result._is_relevant_tb_level(Frame)) + + def testFailFast(self): + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addError(None, None) + self.assertTrue(result.shouldStop) + + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addFailure(None, None) + self.assertTrue(result.shouldStop) + + result = unittest2.TestResult() + result._exc_info_to_string = lambda *_: '' + result.failfast = True + result.addUnexpectedSuccess(None) + self.assertTrue(result.shouldStop) + + def testFailFastSetByRunner(self): + runner = unittest2.TextTestRunner(stream=StringIO(), failfast=True) + self.testRan = False + def test(result): + self.testRan = True + self.assertTrue(result.failfast) + runner.run(test) + self.assertTrue(self.testRan) + + +class TestOutputBuffering(unittest2.TestCase): + + def setUp(self): + self._real_out = sys.stdout + self._real_err = sys.stderr + + def tearDown(self): + sys.stdout = self._real_out + sys.stderr = self._real_err + + def testBufferOutputOff(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest2.TestResult() + self.assertFalse(result.buffer) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + def testBufferOutputStartTestAddSuccess(self): + real_out = self._real_out + real_err = self._real_err + + result = unittest2.TestResult() + self.assertFalse(result.buffer) + + result.buffer = True + + self.assertIs(real_out, sys.stdout) + self.assertIs(real_err, sys.stderr) + + result.startTest(self) + + self.assertIsNot(real_out, sys.stdout) + self.assertIsNot(real_err, sys.stderr) + self.assertIsInstance(sys.stdout, StringIO) + self.assertIsInstance(sys.stderr, StringIO) + self.assertIsNot(sys.stdout, sys.stderr) + + out_stream = sys.stdout + err_stream = sys.stderr + + result._original_stdout = StringIO() + result._original_stderr = StringIO() + + print('foo') + print('bar', file=sys.stderr) + + self.assertEqual(out_stream.getvalue(), 'foo\n') + self.assertEqual(err_stream.getvalue(), 'bar\n') + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + result.addSuccess(self) + result.stopTest(self) + + self.assertIs(sys.stdout, result._original_stdout) + self.assertIs(sys.stderr, result._original_stderr) + + self.assertEqual(result._original_stdout.getvalue(), '') + self.assertEqual(result._original_stderr.getvalue(), '') + + self.assertEqual(out_stream.getvalue(), '') + self.assertEqual(err_stream.getvalue(), '') + + + def getStartedResult(self): + result = unittest2.TestResult() + result.buffer = True + result.startTest(self) + return result + + def testBufferOutputAddErrorOrFailure(self): + for message_attr, add_attr, include_error in [ + ('errors', 'addError', True), + ('failures', 'addFailure', False), + ('errors', 'addError', True), + ('failures', 'addFailure', False) + ]: + result = self.getStartedResult() + result._original_stderr = StringIO() + result._original_stdout = StringIO() + + print('foo') + if include_error: + print('bar', file=sys.stderr) + + addFunction = getattr(result, add_attr) + addFunction(self, (None, None, None)) + result.stopTest(self) + + result_list = getattr(result, message_attr) + self.assertEqual(len(result_list), 1) + + test, message = result_list[0] + expectedOutMessage = textwrap.dedent(""" + Stdout: + foo + """) + expectedErrMessage = '' + if include_error: + expectedErrMessage = textwrap.dedent(""" + Stderr: + bar + """) + expectedFullMessage = 'None\n%s%s' % (expectedOutMessage, expectedErrMessage) + + self.assertIs(test, self) + self.assertEqual(result._original_stdout.getvalue(), expectedOutMessage) + self.assertEqual(result._original_stderr.getvalue(), expectedErrMessage) + self.assertMultiLineEqual(message, expectedFullMessage) + + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_runner.py b/third_party/Python/module/unittest2/unittest2/test/test_runner.py new file mode 100644 index 000000000000..38b39efb0069 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_runner.py @@ -0,0 +1,129 @@ +import pickle + +from cStringIO import StringIO +from unittest2.test.support import LoggingResult, OldTestResult + +import unittest2 + + +class Test_TextTestRunner(unittest2.TestCase): + """Tests for TextTestRunner.""" + + def test_init(self): + runner = unittest2.TextTestRunner() + self.assertFalse(runner.failfast) + self.assertFalse(runner.buffer) + self.assertEqual(runner.verbosity, 1) + self.assertTrue(runner.descriptions) + self.assertEqual(runner.resultclass, unittest2.TextTestResult) + + + def testBufferAndFailfast(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + result = unittest2.TestResult() + runner = unittest2.TextTestRunner(stream=StringIO(), failfast=True, + buffer=True) + # Use our result object + runner._makeResult = lambda: result + runner.run(Test('testFoo')) + + self.assertTrue(result.failfast) + self.assertTrue(result.buffer) + + def testRunnerRegistersResult(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + originalRegisterResult = unittest2.runner.registerResult + def cleanup(): + unittest2.runner.registerResult = originalRegisterResult + self.addCleanup(cleanup) + + result = unittest2.TestResult() + runner = unittest2.TextTestRunner(stream=StringIO()) + # Use our result object + runner._makeResult = lambda: result + + self.wasRegistered = 0 + def fakeRegisterResult(thisResult): + self.wasRegistered += 1 + self.assertEqual(thisResult, result) + unittest2.runner.registerResult = fakeRegisterResult + + runner.run(unittest2.TestSuite()) + self.assertEqual(self.wasRegistered, 1) + + def test_works_with_result_without_startTestRun_stopTestRun(self): + class OldTextResult(OldTestResult): + def __init__(self, *_): + super(OldTextResult, self).__init__() + separator2 = '' + def printErrors(self): + pass + + runner = unittest2.TextTestRunner(stream=StringIO(), + resultclass=OldTextResult) + runner.run(unittest2.TestSuite()) + + def test_startTestRun_stopTestRun_called(self): + class LoggingTextResult(LoggingResult): + separator2 = '' + def printErrors(self): + pass + + class LoggingRunner(unittest2.TextTestRunner): + def __init__(self, events): + super(LoggingRunner, self).__init__(StringIO()) + self._events = events + + def _makeResult(self): + return LoggingTextResult(self._events) + + events = [] + runner = LoggingRunner(events) + runner.run(unittest2.TestSuite()) + expected = ['startTestRun', 'stopTestRun'] + self.assertEqual(events, expected) + + def test_pickle_unpickle(self): + # Issue #7197: a TextTestRunner should be (un)pickleable. This is + # required by test_multiprocessing under Windows (in verbose mode). + import StringIO + # cStringIO objects are not pickleable, but StringIO objects are. + stream = StringIO.StringIO("foo") + runner = unittest2.TextTestRunner(stream) + for protocol in range(pickle.HIGHEST_PROTOCOL + 1): + s = pickle.dumps(runner, protocol=protocol) + obj = pickle.loads(s) + # StringIO objects never compare equal, a cheap test instead. + self.assertEqual(obj.stream.getvalue(), stream.getvalue()) + + def test_resultclass(self): + def MockResultClass(*args): + return args + STREAM = object() + DESCRIPTIONS = object() + VERBOSITY = object() + runner = unittest2.TextTestRunner(STREAM, DESCRIPTIONS, VERBOSITY, + resultclass=MockResultClass) + self.assertEqual(runner.resultclass, MockResultClass) + + expectedresult = (runner.stream, DESCRIPTIONS, VERBOSITY) + self.assertEqual(runner._makeResult(), expectedresult) + + + def test_oldresult(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + runner = unittest2.TextTestRunner(resultclass=OldTestResult, + stream=StringIO()) + # This will raise an exception if TextTestRunner can't handle old + # test result objects + runner.run(Test('testFoo')) + + +if __name__ == '__main__': + unittest2.main()
\ No newline at end of file diff --git a/third_party/Python/module/unittest2/unittest2/test/test_setups.py b/third_party/Python/module/unittest2/unittest2/test/test_setups.py new file mode 100644 index 000000000000..55c7f578df10 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_setups.py @@ -0,0 +1,502 @@ +import sys + +from cStringIO import StringIO + +import unittest2 +from unittest2.test.support import resultFactory + + +class TestSetups(unittest2.TestCase): + + def getRunner(self): + return unittest2.TextTestRunner(resultclass=resultFactory, + stream=StringIO()) + def runTests(self, *cases): + suite = unittest2.TestSuite() + for case in cases: + tests = unittest2.defaultTestLoader.loadTestsFromTestCase(case) + suite.addTests(tests) + + runner = self.getRunner() + + # creating a nested suite exposes some potential bugs + realSuite = unittest2.TestSuite() + realSuite.addTest(suite) + # adding empty suites to the end exposes potential bugs + suite.addTest(unittest2.TestSuite()) + realSuite.addTest(unittest2.TestSuite()) + return runner.run(realSuite) + + def test_setup_class(self): + class Test(unittest2.TestCase): + setUpCalled = 0 + @classmethod + def setUpClass(cls): + Test.setUpCalled += 1 + unittest2.TestCase.setUpClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.setUpCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class(self): + class Test(unittest2.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_teardown_class_two_classes(self): + class Test(unittest2.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + tearDownCalled = 0 + @classmethod + def tearDownClass(cls): + Test2.tearDownCalled += 1 + unittest2.TestCase.tearDownClass() + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + + self.assertEqual(Test.tearDownCalled, 1) + self.assertEqual(Test2.tearDownCalled, 1) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setupclass(self): + class BrokenTest(unittest2.TestCase): + @classmethod + def setUpClass(cls): + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(BrokenTest) + + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), + 'setUpClass (%s.BrokenTest)' % __name__) + + def test_error_in_teardown_class(self): + class Test(unittest2.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + tornDown = 0 + @classmethod + def tearDownClass(cls): + Test2.tornDown += 1 + raise TypeError('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test, Test2) + self.assertEqual(result.testsRun, 4) + self.assertEqual(len(result.errors), 2) + self.assertEqual(Test.tornDown, 1) + self.assertEqual(Test2.tornDown, 1) + + error, _ = result.errors[0] + self.assertEqual(str(error), + 'tearDownClass (%s.Test)' % __name__) + + def test_class_not_torndown_when_setup_fails(self): + class Test(unittest2.TestCase): + tornDown = False + @classmethod + def setUpClass(cls): + raise TypeError + @classmethod + def tearDownClass(cls): + Test.tornDown = True + raise TypeError('foo') + def test_one(self): + pass + + self.runTests(Test) + self.assertFalse(Test.tornDown) + + def test_class_not_setup_or_torndown_when_skipped(self): + class Test(unittest2.TestCase): + classSetUp = False + tornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.tornDown = True + def test_one(self): + pass + + Test = unittest2.skip("hop")(Test) + self.runTests(Test) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.tornDown) + + def test_setup_teardown_order_with_pathological_suite(self): + results = [] + + class Module1(object): + @staticmethod + def setUpModule(): + results.append('Module1.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module1.tearDownModule') + + class Module2(object): + @staticmethod + def setUpModule(): + results.append('Module2.setUpModule') + @staticmethod + def tearDownModule(): + results.append('Module2.tearDownModule') + + class Test1(unittest2.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 1') + @classmethod + def tearDownClass(cls): + results.append('teardown 1') + def testOne(self): + results.append('Test1.testOne') + def testTwo(self): + results.append('Test1.testTwo') + + class Test2(unittest2.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 2') + @classmethod + def tearDownClass(cls): + results.append('teardown 2') + def testOne(self): + results.append('Test2.testOne') + def testTwo(self): + results.append('Test2.testTwo') + + class Test3(unittest2.TestCase): + @classmethod + def setUpClass(cls): + results.append('setup 3') + @classmethod + def tearDownClass(cls): + results.append('teardown 3') + def testOne(self): + results.append('Test3.testOne') + def testTwo(self): + results.append('Test3.testTwo') + + Test1.__module__ = Test2.__module__ = 'Module' + Test3.__module__ = 'Module2' + sys.modules['Module'] = Module1 + sys.modules['Module2'] = Module2 + + first = unittest2.TestSuite((Test1('testOne'),)) + second = unittest2.TestSuite((Test1('testTwo'),)) + third = unittest2.TestSuite((Test2('testOne'),)) + fourth = unittest2.TestSuite((Test2('testTwo'),)) + fifth = unittest2.TestSuite((Test3('testOne'),)) + sixth = unittest2.TestSuite((Test3('testTwo'),)) + suite = unittest2.TestSuite((first, second, third, fourth, fifth, sixth)) + + runner = self.getRunner() + result = runner.run(suite) + self.assertEqual(result.testsRun, 6) + self.assertEqual(len(result.errors), 0) + + self.assertEqual(results, + ['Module1.setUpModule', 'setup 1', + 'Test1.testOne', 'Test1.testTwo', 'teardown 1', + 'setup 2', 'Test2.testOne', 'Test2.testTwo', + 'teardown 2', 'Module1.tearDownModule', + 'Module2.setUpModule', 'setup 3', + 'Test3.testOne', 'Test3.testTwo', + 'teardown 3', 'Module2.tearDownModule']) + + def test_setup_module(self): + class Module(object): + moduleSetup = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_setup_module(self): + class Module(object): + moduleSetup = 0 + moduleTornDown = 0 + @staticmethod + def setUpModule(): + Module.moduleSetup += 1 + raise TypeError('foo') + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest2.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleSetup, 1) + self.assertEqual(Module.moduleTornDown, 0) + self.assertEqual(result.testsRun, 0) + self.assertFalse(Test.classSetUp) + self.assertFalse(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'setUpModule (Module)') + + def test_testcase_with_missing_module(self): + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules.pop('Module', None) + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 2) + + def test_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 2) + self.assertEqual(len(result.errors), 0) + + def test_error_in_teardown_module(self): + class Module(object): + moduleTornDown = 0 + @staticmethod + def tearDownModule(): + Module.moduleTornDown += 1 + raise TypeError('foo') + + class Test(unittest2.TestCase): + classSetUp = False + classTornDown = False + @classmethod + def setUpClass(cls): + Test.classSetUp = True + @classmethod + def tearDownClass(cls): + Test.classTornDown = True + def test_one(self): + pass + def test_two(self): + pass + + class Test2(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + Test.__module__ = 'Module' + Test2.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test, Test2) + self.assertEqual(Module.moduleTornDown, 1) + self.assertEqual(result.testsRun, 4) + self.assertTrue(Test.classSetUp) + self.assertTrue(Test.classTornDown) + self.assertEqual(len(result.errors), 1) + error, _ = result.errors[0] + self.assertEqual(str(error), 'tearDownModule (Module)') + + def test_skiptest_in_setupclass(self): + class Test(unittest2.TestCase): + @classmethod + def setUpClass(cls): + raise unittest2.SkipTest('foo') + def test_one(self): + pass + def test_two(self): + pass + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), 'setUpClass (%s.Test)' % __name__) + + def test_skiptest_in_setupmodule(self): + class Test(unittest2.TestCase): + def test_one(self): + pass + def test_two(self): + pass + + class Module(object): + @staticmethod + def setUpModule(): + raise unittest2.SkipTest('foo') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + result = self.runTests(Test) + self.assertEqual(result.testsRun, 0) + self.assertEqual(len(result.errors), 0) + self.assertEqual(len(result.skipped), 1) + skipped = result.skipped[0][0] + self.assertEqual(str(skipped), 'setUpModule (Module)') + + def test_suite_debug_executes_setups_and_teardowns(self): + ordering = [] + + class Module(object): + @staticmethod + def setUpModule(): + ordering.append('setUpModule') + @staticmethod + def tearDownModule(): + ordering.append('tearDownModule') + + class Test(unittest2.TestCase): + @classmethod + def setUpClass(cls): + ordering.append('setUpClass') + @classmethod + def tearDownClass(cls): + ordering.append('tearDownClass') + def test_something(self): + ordering.append('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + suite = unittest2.defaultTestLoader.loadTestsFromTestCase(Test) + suite.debug() + expectedOrder = ['setUpModule', 'setUpClass', 'test_something', 'tearDownClass', 'tearDownModule'] + self.assertEqual(ordering, expectedOrder) + + def test_suite_debug_propagates_exceptions(self): + class Module(object): + @staticmethod + def setUpModule(): + if phase == 0: + raise Exception('setUpModule') + @staticmethod + def tearDownModule(): + if phase == 1: + raise Exception('tearDownModule') + + class Test(unittest2.TestCase): + @classmethod + def setUpClass(cls): + if phase == 2: + raise Exception('setUpClass') + @classmethod + def tearDownClass(cls): + if phase == 3: + raise Exception('tearDownClass') + def test_something(self): + if phase == 4: + raise Exception('test_something') + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + + _suite = unittest2.defaultTestLoader.loadTestsFromTestCase(Test) + suite = unittest2.TestSuite() + + # nesting a suite again exposes a bug in the initial implementation + suite.addTest(_suite) + messages = ('setUpModule', 'tearDownModule', 'setUpClass', 'tearDownClass', 'test_something') + for phase, msg in enumerate(messages): + self.assertRaisesRegexp(Exception, msg, suite.debug) diff --git a/third_party/Python/module/unittest2/unittest2/test/test_skipping.py b/third_party/Python/module/unittest2/unittest2/test/test_skipping.py new file mode 100644 index 000000000000..9555b9ff7700 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_skipping.py @@ -0,0 +1,143 @@ +from unittest2.test.support import LoggingResult + +import unittest2 + + +class Test_TestSkipping(unittest2.TestCase): + + def test_skipping(self): + class Foo(unittest2.TestCase): + def test_skip_me(self): + self.skipTest("skip") + events = [] + result = LoggingResult(events) + test = Foo("test_skip_me") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "skip")]) + + # Try letting setUp skip the test now. + class Foo(unittest2.TestCase): + def setUp(self): + self.skipTest("testing") + def test_nothing(self): pass + events = [] + result = LoggingResult(events) + test = Foo("test_nothing") + test.run(result) + self.assertEqual(events, ['startTest', 'addSkip', 'stopTest']) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(result.testsRun, 1) + + def test_skipping_decorators(self): + op_table = ((unittest2.skipUnless, False, True), + (unittest2.skipIf, True, False)) + for deco, do_skip, dont_skip in op_table: + class Foo(unittest2.TestCase): + @deco(do_skip, "testing") + def test_skip(self): + pass + + @deco(dont_skip, "testing") + def test_dont_skip(self): + pass + + test_do_skip = Foo("test_skip") + test_dont_skip = Foo("test_dont_skip") + suite = unittest2.TestSuite([test_do_skip, test_dont_skip]) + events = [] + result = LoggingResult(events) + suite.run(result) + self.assertEqual(len(result.skipped), 1) + expected = ['startTest', 'addSkip', 'stopTest', + 'startTest', 'addSuccess', 'stopTest'] + self.assertEqual(events, expected) + self.assertEqual(result.testsRun, 2) + self.assertEqual(result.skipped, [(test_do_skip, "testing")]) + self.assertTrue(result.wasSuccessful()) + + def test_skip_class(self): + class Foo(unittest2.TestCase): + def test_1(self): + record.append(1) + + # was originally a class decorator... + Foo = unittest2.skip("testing")(Foo) + record = [] + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertEqual(record, []) + + def test_expected_failure(self): + class Foo(unittest2.TestCase): + @unittest2.expectedFailure + def test_die(self): + self.fail("help me!") + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addExpectedFailure', 'stopTest']) + self.assertEqual(result.expectedFailures[0][0], test) + self.assertTrue(result.wasSuccessful()) + + def test_unexpected_success(self): + class Foo(unittest2.TestCase): + @unittest2.expectedFailure + def test_die(self): + pass + events = [] + result = LoggingResult(events) + test = Foo("test_die") + test.run(result) + self.assertEqual(events, + ['startTest', 'addUnexpectedSuccess', 'stopTest']) + self.assertFalse(result.failures) + self.assertEqual(result.unexpectedSuccesses, [test]) + self.assertTrue(result.wasSuccessful()) + + def test_skip_doesnt_run_setup(self): + class Foo(unittest2.TestCase): + wasSetUp = False + wasTornDown = False + def setUp(self): + Foo.wasSetUp = True + def tornDown(self): + Foo.wasTornDown = True + @unittest2.skip('testing') + def test_1(self): + pass + + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + self.assertFalse(Foo.wasSetUp) + self.assertFalse(Foo.wasTornDown) + + def test_decorated_skip(self): + def decorator(func): + def inner(*a): + return func(*a) + return inner + + class Foo(unittest2.TestCase): + @decorator + @unittest2.skip('testing') + def test_1(self): + pass + + result = unittest2.TestResult() + test = Foo("test_1") + suite = unittest2.TestSuite([test]) + suite.run(result) + self.assertEqual(result.skipped, [(test, "testing")]) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_suite.py b/third_party/Python/module/unittest2/unittest2/test/test_suite.py new file mode 100644 index 000000000000..64a72bbdf147 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_suite.py @@ -0,0 +1,341 @@ +from unittest2.test.support import EqualityMixin, LoggingResult + +import sys +import unittest2 + +class Test(object): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + def test_3(self): pass + def runTest(self): pass + +def _mk_TestSuite(*names): + return unittest2.TestSuite(Test.Foo(n) for n in names) + + +class Test_TestSuite(unittest2.TestCase, EqualityMixin): + + ### Set up attributes needed by inherited tests + ################################################################ + + # Used by EqualityMixin.test_eq + eq_pairs = [(unittest2.TestSuite(), unittest2.TestSuite()), + (unittest2.TestSuite(), unittest2.TestSuite([])), + (_mk_TestSuite('test_1'), _mk_TestSuite('test_1'))] + + # Used by EqualityMixin.test_ne + ne_pairs = [(unittest2.TestSuite(), _mk_TestSuite('test_1')), + (unittest2.TestSuite([]), _mk_TestSuite('test_1')), + (_mk_TestSuite('test_1', 'test_2'), _mk_TestSuite('test_1', 'test_3')), + (_mk_TestSuite('test_1'), _mk_TestSuite('test_2'))] + + ################################################################ + ### /Set up attributes needed by inherited tests + + ### Tests for TestSuite.__init__ + ################################################################ + + # "class TestSuite([tests])" + # + # The tests iterable should be optional + def test_init__tests_optional(self): + suite = unittest2.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should deal with empty tests iterables by allowing the + # creation of an empty suite + def test_init__empty_tests(self): + suite = unittest2.TestSuite([]) + + self.assertEqual(suite.countTestCases(), 0) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # TestSuite should allow any iterable to provide tests + def test_init__tests_from_any_iterable(self): + def tests(): + yield unittest2.FunctionTestCase(lambda: None) + yield unittest2.FunctionTestCase(lambda: None) + + suite_1 = unittest2.TestSuite(tests()) + self.assertEqual(suite_1.countTestCases(), 2) + + suite_2 = unittest2.TestSuite(suite_1) + self.assertEqual(suite_2.countTestCases(), 2) + + suite_3 = unittest2.TestSuite(set(suite_1)) + self.assertEqual(suite_3.countTestCases(), 2) + + # "class TestSuite([tests])" + # ... + # "If tests is given, it must be an iterable of individual test cases + # or other test suites that will be used to build the suite initially" + # + # Does TestSuite() also allow other TestSuite() instances to be present + # in the tests iterable? + def test_init__TestSuite_instances_in_tests(self): + def tests(): + ftc = unittest2.FunctionTestCase(lambda: None) + yield unittest2.TestSuite([ftc]) + yield unittest2.FunctionTestCase(lambda: None) + + suite = unittest2.TestSuite(tests()) + self.assertEqual(suite.countTestCases(), 2) + + ################################################################ + ### /Tests for TestSuite.__init__ + + # Container types should support the iter protocol + def test_iter(self): + test1 = unittest2.FunctionTestCase(lambda: None) + test2 = unittest2.FunctionTestCase(lambda: None) + suite = unittest2.TestSuite((test1, test2)) + + self.assertEqual(list(suite), [test1, test2]) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite returns 0? + def test_countTestCases_zero_simple(self): + suite = unittest2.TestSuite() + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Presumably an empty TestSuite (even if it contains other empty + # TestSuite instances) returns 0? + def test_countTestCases_zero_nested(self): + class Test1(unittest2.TestCase): + def test(self): + pass + + suite = unittest2.TestSuite([unittest2.TestSuite()]) + + self.assertEqual(suite.countTestCases(), 0) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + def test_countTestCases_simple(self): + test1 = unittest2.FunctionTestCase(lambda: None) + test2 = unittest2.FunctionTestCase(lambda: None) + suite = unittest2.TestSuite((test1, test2)) + + self.assertEqual(suite.countTestCases(), 2) + + # "Return the number of tests represented by the this test object. + # ...this method is also implemented by the TestSuite class, which can + # return larger [greater than 1] values" + # + # Make sure this holds for nested TestSuite instances, too + def test_countTestCases_nested(self): + class Test1(unittest2.TestCase): + def test1(self): pass + def test2(self): pass + + test2 = unittest2.FunctionTestCase(lambda: None) + test3 = unittest2.FunctionTestCase(lambda: None) + child = unittest2.TestSuite((Test1('test2'), test2)) + parent = unittest2.TestSuite((test3, child, Test1('test1'))) + + self.assertEqual(parent.countTestCases(), 4) + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + # + # And if there are no tests? What then? + def test_run__empty_suite(self): + events = [] + result = LoggingResult(events) + + suite = unittest2.TestSuite() + + suite.run(result) + + self.assertEqual(events, []) + + # "Note that unlike TestCase.run(), TestSuite.run() requires the + # "result object to be passed in." + def test_run__requires_result(self): + suite = unittest2.TestSuite() + + try: + suite.run() + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + # "Run the tests associated with this suite, collecting the result into + # the test result object passed as result." + def test_run(self): + events = [] + result = LoggingResult(events) + + class LoggingCase(unittest2.TestCase): + def run(self, result): + events.append('run %s' % self._testMethodName) + + def test1(self): pass + def test2(self): pass + + tests = [LoggingCase('test1'), LoggingCase('test2')] + + unittest2.TestSuite(tests).run(result) + + self.assertEqual(events, ['run test1', 'run test2']) + + # "Add a TestCase ... to the suite" + def test_addTest__TestCase(self): + class Foo(unittest2.TestCase): + def test(self): pass + + test = Foo('test') + suite = unittest2.TestSuite() + + suite.addTest(test) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [test]) + + # "Add a ... TestSuite to the suite" + def test_addTest__TestSuite(self): + class Foo(unittest2.TestCase): + def test(self): pass + + suite_2 = unittest2.TestSuite([Foo('test')]) + + suite = unittest2.TestSuite() + suite.addTest(suite_2) + + self.assertEqual(suite.countTestCases(), 1) + self.assertEqual(list(suite), [suite_2]) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + def test_addTests(self): + class Foo(unittest2.TestCase): + def test_1(self): pass + def test_2(self): pass + + test_1 = Foo('test_1') + test_2 = Foo('test_2') + inner_suite = unittest2.TestSuite([test_2]) + + def gen(): + yield test_1 + yield test_2 + yield inner_suite + + suite_1 = unittest2.TestSuite() + suite_1.addTests(gen()) + + self.assertEqual(list(suite_1), list(gen())) + + # "This is equivalent to iterating over tests, calling addTest() for + # each element" + suite_2 = unittest2.TestSuite() + for t in gen(): + suite_2.addTest(t) + + self.assertEqual(suite_1, suite_2) + + # "Add all the tests from an iterable of TestCase and TestSuite + # instances to this test suite." + # + # What happens if it doesn't get an iterable? + def test_addTest__noniterable(self): + suite = unittest2.TestSuite() + + try: + suite.addTests(5) + except TypeError: + pass + else: + self.fail("Failed to raise TypeError") + + def test_addTest__noncallable(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTest, 5) + + def test_addTest__casesuiteclass(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTest, Test_TestSuite) + self.assertRaises(TypeError, suite.addTest, unittest2.TestSuite) + + def test_addTests__string(self): + suite = unittest2.TestSuite() + self.assertRaises(TypeError, suite.addTests, "foo") + + def test_function_in_suite(self): + def f(_): + pass + suite = unittest2.TestSuite() + suite.addTest(f) + + # when the bug is fixed this line will not crash + suite.run(unittest2.TestResult()) + + + def test_basetestsuite(self): + class Test(unittest2.TestCase): + wasSetUp = False + wasTornDown = False + @classmethod + def setUpClass(cls): + cls.wasSetUp = True + @classmethod + def tearDownClass(cls): + cls.wasTornDown = True + def testPass(self): + pass + def testFail(self): + fail + class Module(object): + wasSetUp = False + wasTornDown = False + @staticmethod + def setUpModule(): + Module.wasSetUp = True + @staticmethod + def tearDownModule(): + Module.wasTornDown = True + + Test.__module__ = 'Module' + sys.modules['Module'] = Module + self.addCleanup(sys.modules.pop, 'Module') + + suite = unittest2.BaseTestSuite() + suite.addTests([Test('testPass'), Test('testFail')]) + self.assertEqual(suite.countTestCases(), 2) + + result = unittest2.TestResult() + suite.run(result) + self.assertFalse(Module.wasSetUp) + self.assertFalse(Module.wasTornDown) + self.assertFalse(Test.wasSetUp) + self.assertFalse(Test.wasTornDown) + self.assertEqual(len(result.errors), 1) + self.assertEqual(len(result.failures), 0) + self.assertEqual(result.testsRun, 2) + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/test/test_unittest2_with.py b/third_party/Python/module/unittest2/unittest2/test/test_unittest2_with.py new file mode 100644 index 000000000000..ddb88ace82ae --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/test/test_unittest2_with.py @@ -0,0 +1,143 @@ +from __future__ import with_statement + +import unittest2 +from unittest2.test.support import OldTestResult, catch_warnings + +import warnings +# needed to enable the deprecation warnings +warnings.simplefilter('default') + +class TestWith(unittest2.TestCase): + """Tests that use the with statement live in this + module so that all other tests can be run with Python 2.4. + """ + + def testAssertRaisesExcValue(self): + class ExceptionMock(Exception): + pass + + def Stub(foo): + raise ExceptionMock(foo) + v = "particular value" + + ctx = self.assertRaises(ExceptionMock) + with ctx: + Stub(v) + e = ctx.exception + self.assertIsInstance(e, ExceptionMock) + self.assertEqual(e.args[0], v) + + + def test_assertRaises(self): + def _raise(e): + raise e + self.assertRaises(KeyError, _raise, KeyError) + self.assertRaises(KeyError, _raise, KeyError("key")) + try: + self.assertRaises(KeyError, lambda: None) + except self.failureException as e: + self.assertIn("KeyError not raised", e.args) + else: + self.fail("assertRaises() didn't fail") + try: + self.assertRaises(KeyError, _raise, ValueError) + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + with self.assertRaises(KeyError) as cm: + try: + raise KeyError + except Exception as e: + raise + self.assertIs(cm.exception, e) + + with self.assertRaises(KeyError): + raise KeyError("key") + try: + with self.assertRaises(KeyError): + pass + except self.failureException as e: + self.assertIn("KeyError not raised", e.args) + else: + self.fail("assertRaises() didn't fail") + try: + with self.assertRaises(KeyError): + raise ValueError + except ValueError: + pass + else: + self.fail("assertRaises() didn't let exception pass through") + + def test_assert_dict_unicode_error(self): + with catch_warnings(record=True): + # This causes a UnicodeWarning due to its craziness + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing the failure msg + with self.assertRaises(self.failureException): + self.assertDictContainsSubset({'foo': one}, {'foo': u'\uFFFD'}) + + def test_formatMessage_unicode_error(self): + with catch_warnings(record=True): + # This causes a UnicodeWarning due to its craziness + one = ''.join(chr(i) for i in range(255)) + # this used to cause a UnicodeDecodeError constructing msg + self._formatMessage(one, u'\uFFFD') + + def assertOldResultWarning(self, test, failures): + with catch_warnings(record=True) as log: + result = OldTestResult() + test.run(result) + self.assertEqual(len(result.failures), failures) + warning, = log + self.assertIs(warning.category, DeprecationWarning) + + def test_old_testresult(self): + class Test(unittest2.TestCase): + def testSkip(self): + self.skipTest('foobar') + @unittest2.expectedFailure + def testExpectedFail(self): + raise TypeError + @unittest2.expectedFailure + def testUnexpectedSuccess(self): + pass + + for test_name, should_pass in (('testSkip', True), + ('testExpectedFail', True), + ('testUnexpectedSuccess', False)): + test = Test(test_name) + self.assertOldResultWarning(test, int(not should_pass)) + + def test_old_testresult_setup(self): + class Test(unittest2.TestCase): + def setUp(self): + self.skipTest('no reason') + def testFoo(self): + pass + self.assertOldResultWarning(Test('testFoo'), 0) + + def test_old_testresult_class(self): + class Test(unittest2.TestCase): + def testFoo(self): + pass + Test = unittest2.skip('no reason')(Test) + self.assertOldResultWarning(Test('testFoo'), 0) + + def testPendingDeprecationMethodNames(self): + """Test fail* methods pending deprecation, they will warn in 3.2. + + Do not use these methods. They will go away in 3.3. + """ + with catch_warnings(record=True): + self.failIfEqual(3, 5) + self.failUnlessEqual(3, 3) + self.failUnlessAlmostEqual(2.0, 2.0) + self.failIfAlmostEqual(3.0, 5.0) + self.failUnless(True) + self.failUnlessRaises(TypeError, lambda _: 3.14 + u'spam') + self.failIf(False) + + +if __name__ == '__main__': + unittest2.main() diff --git a/third_party/Python/module/unittest2/unittest2/util.py b/third_party/Python/module/unittest2/unittest2/util.py new file mode 100644 index 000000000000..c45d008cc886 --- /dev/null +++ b/third_party/Python/module/unittest2/unittest2/util.py @@ -0,0 +1,99 @@ +"""Various utility functions.""" + +__unittest = True + + +_MAX_LENGTH = 80 +def safe_repr(obj, short=False): + try: + result = repr(obj) + except Exception: + result = object.__repr__(obj) + if not short or len(result) < _MAX_LENGTH: + return result + return result[:_MAX_LENGTH] + ' [truncated]...' + +def safe_str(obj): + try: + return str(obj) + except Exception: + return object.__str__(obj) + +def strclass(cls): + return "%s.%s" % (cls.__module__, cls.__name__) + +def sorted_list_difference(expected, actual): + """Finds elements in only one or the other of two, sorted input lists. + + Returns a two-element tuple of lists. The first list contains those + elements in the "expected" list but not in the "actual" list, and the + second contains those elements in the "actual" list but not in the + "expected" list. Duplicate elements in either input list are ignored. + """ + i = j = 0 + missing = [] + unexpected = [] + while True: + try: + e = expected[i] + a = actual[j] + if e < a: + missing.append(e) + i += 1 + while expected[i] == e: + i += 1 + elif e > a: + unexpected.append(a) + j += 1 + while actual[j] == a: + j += 1 + else: + i += 1 + try: + while expected[i] == e: + i += 1 + finally: + j += 1 + while actual[j] == a: + j += 1 + except IndexError: + missing.extend(expected[i:]) + unexpected.extend(actual[j:]) + break + return missing, unexpected + +def unorderable_list_difference(expected, actual, ignore_duplicate=False): + """Same behavior as sorted_list_difference but + for lists of unorderable items (like dicts). + + As it does a linear search per item (remove) it + has O(n*n) performance. + """ + missing = [] + unexpected = [] + while expected: + item = expected.pop() + try: + actual.remove(item) + except ValueError: + missing.append(item) + if ignore_duplicate: + for lst in expected, actual: + try: + while True: + lst.remove(item) + except ValueError: + pass + if ignore_duplicate: + while actual: + item = actual.pop() + unexpected.append(item) + try: + while True: + actual.remove(item) + except ValueError: + pass + return missing, unexpected + + # anything left in actual is unexpected + return missing, actual |