"""idlelib.config -- Manage IDLE configuration information. The comments at the beginning of config-main.def describe the configuration files and the design implemented to update user configuration information. In particular, user configuration choices which duplicate the defaults will be removed from the user's configuration files, and if a user file becomes empty, it will be deleted. The configuration database maps options to values. Conceptually, the database keys are tuples (config-type, section, item). As implemented, there are separate dicts for default and user values. Each has config-type keys 'main', 'extensions', 'highlight', and 'keys'. The value for each key is a ConfigParser instance that maps section and item to values. For 'main' and 'extensions', user values override default values. For 'highlight' and 'keys', user sections augment the default sections (and must, therefore, have distinct names). Throughout this module there is an emphasis on returning useable defaults when a problem occurs in returning a requested configuration value back to idle. This is to allow IDLE to continue to function in spite of errors in the retrieval of config information. When a default is returned instead of a requested config value, a message is printed to stderr to aid in configuration problem notification and resolution. """ # TODOs added Oct 2014, tjr from configparser import ConfigParser import os import sys from tkinter.font import Font import idlelib class InvalidConfigType(Exception): pass class InvalidConfigSet(Exception): pass class InvalidTheme(Exception): pass class IdleConfParser(ConfigParser): """ A ConfigParser specialised for idle configuration file handling """ def __init__(self, cfgFile, cfgDefaults=None): """ cfgFile - string, fully specified configuration file name """ self.file = cfgFile # This is currently '' when testing. ConfigParser.__init__(self, defaults=cfgDefaults, strict=False) def Get(self, section, option, type=None, default=None, raw=False): """ Get an option value for given section/option or return default. If type is specified, return as type. """ # TODO Use default as fallback, at least if not None # Should also print Warning(file, section, option). # Currently may raise ValueError if not self.has_option(section, option): return default if type == 'bool': return self.getboolean(section, option) elif type == 'int': return self.getint(section, option) else: return self.get(section, option, raw=raw) def GetOptionList(self, section): "Return a list of options for given section, else []." if self.has_section(section): return self.options(section) else: #return a default value return [] def Load(self): "Load the configuration file from disk." if self.file: self.read(self.file) class IdleUserConfParser(IdleConfParser): """ IdleConfigParser specialised for user configuration handling. """ def SetOption(self, section, option, value): """Return True if option is added or changed to value, else False. Add section if required. False means option already had value. """ if self.has_option(section, option): if self.get(section, option) == value: return False else: self.set(section, option, value) return True else: if not self.has_section(section): self.add_section(section) self.set(section, option, value) return True def RemoveOption(self, section, option): """Return True if option is removed from section, else False. False if either section does not exist or did not have option. """ if self.has_section(section): return self.remove_option(section, option) return False def AddSection(self, section): "If section doesn't exist, add it." if not self.has_section(section): self.add_section(section) def RemoveEmptySections(self): "Remove any sections that have no options." for section in self.sections(): if not self.GetOptionList(section): self.remove_section(section) def IsEmpty(self): "Return True if no sections after removing empty sections." self.RemoveEmptySections() return not self.sections() def Save(self): """Update user configuration file. If self not empty after removing empty sections, write the file to disk. Otherwise, remove the file from disk if it exists. """ fname = self.file if fname and fname[0] != '#': if not self.IsEmpty(): try: cfgFile = open(fname, 'w') except OSError: os.unlink(fname) cfgFile = open(fname, 'w') with cfgFile: self.write(cfgFile) elif os.path.exists(self.file): os.remove(self.file) class IdleConf: """Hold config parsers for all idle config files in singleton instance. Default config files, self.defaultCfg -- for config_type in self.config_types: (idle install dir)/config-{config-type}.def User config files, self.userCfg -- for config_type in self.config_types: (user home dir)/.idlerc/config-{config-type}.cfg """ def __init__(self, _utest=False): self.config_types = ('main', 'highlight', 'keys', 'extensions') self.defaultCfg = {} self.userCfg = {} self.cfg = {} # TODO use to select userCfg vs defaultCfg # self.blink_off_time = ['insertofftime'] # See https:/bugs.python.org/issue4630, msg356516. if not _utest: self.CreateConfigHandlers() self.LoadCfgFiles() def CreateConfigHandlers(self): "Populate default and user config parser dictionaries." idledir = os.path.dirname(__file__) self.userdir = userdir = '' if idlelib.testing else self.GetUserCfgDir() for cfg_type in self.config_types: self.defaultCfg[cfg_type] = IdleConfParser( os.path.join(idledir, f'config-{cfg_type}.def')) self.userCfg[cfg_type] = IdleUserConfParser( os.path.join(userdir or '#', f'config-{cfg_type}.cfg')) def GetUserCfgDir(self): """Return a filesystem directory for storing user config files. Creates it if required. """ cfgDir = '.idlerc' userDir = os.path.expanduser('~') if userDir != '~': # expanduser() found user home dir if not os.path.exists(userDir): if not idlelib.testing: warn = ('\n Warning: os.path.expanduser("~") points to\n ' + userDir + ',\n but the path does not exist.') try: print(warn, file=sys.stderr) except OSError: pass userDir = '~' if userDir == "~": # still no path to home! # traditionally IDLE has defaulted to os.getcwd(), is this adequate? userDir = os.getcwd() userDir = os.path.join(userDir, cfgDir) if not os.path.exists(userDir): try: os.mkdir(userDir) except OSError: if not idlelib.testing: warn = ('\n Warning: unable to create user config directory\n' + userDir + '\n Check path and permissions.\n Exiting!\n') try: print(warn, file=sys.stderr) except OSError: pass raise SystemExit # TODO continue without userDIr instead of exit return userDir def GetOption(self, configType, section, option, default=None, type=None, warn_on_default=True, raw=False): """Return a value for configType section option, or default. If type is not None, return a value of that type. Also pass raw to the config parser. First try to return a valid value (including type) from a user configuration. If that fails, try the default configuration. If that fails, return default, with a default of None. Warn if either user or default configurations have an invalid value. Warn if default is returned and warn_on_default is True. """ try: if self.userCfg[configType].has_option(section, option): return self.userCfg[configType].Get(section, option, type=type, raw=raw) except ValueError: warning = ('\n Warning: config.py - IdleConf.GetOption -\n' ' invalid %r value for configuration option %r\n' ' from section %r: %r' % (type, option, section, self.userCfg[configType].Get(section, option, raw=raw))) _warn(warning, configType, section, option) try: if self.defaultCfg[configType].has_option(section,option): return self.defaultCfg[configType].Get( section, option, type=type, raw=raw) except ValueError: pass #returning default, print warning if warn_on_default: warning = ('\n Warning: config.py - IdleConf.GetOption -\n' ' problem retrieving configuration option %r\n' ' from section %r.\n' ' returning default value: %r' % (option, section, default)) _warn(warning, configType, section, option) return default def SetOption(self, configType, section, option, value): """Set section option to value in user config file.""" self.userCfg[configType].SetOption(section, option, value) def GetSectionList(self, configSet, configType): """Return sections for configSet configType configuration. configSet must be either 'user' or 'default' configType must be in self.config_types. """ if not (configType in self.config_types): raise InvalidConfigType('Invalid configType specified') if configSet == 'user': cfgParser = self.userCfg[configType] elif configSet == 'default': cfgParser=self.defaultCfg[configType] else: raise InvalidConfigSet('Invalid configSet specified') return cfgParser.sections() def GetHighlight(self, theme, element): """Return dict of theme element highlight colors. The keys are 'foreground' and 'background'. The values are tkinter color strings for configuring backgrounds and tags. """ cfg = ('default' if self.defaultCfg['highlight'].has_section(theme) else 'user') theme_dict = self.GetThemeDict(cfg, theme) fore = theme_dict[element + '-foreground'] if element == 'cursor': element = 'normal' back = theme_dict[element + '-background'] return {"foreground": fore, "background": back} def GetThemeDict(self, type, themeName): """Return {option:value} dict for elements in themeName. type - string, 'default' or 'user' theme type themeName - string, theme name Values are loaded over ultimate fallback defaults to guarantee that all theme elements are present in a newly created theme. """ if type == 'user': cfgParser = self.userCfg['highlight'] elif type == 'default': cfgParser = self.defaultCfg['highlight'] else: raise InvalidTheme('Invalid theme type specified') # Provide foreground and background colors for each theme # element (other than cursor) even though some values are not # yet used by idle, to allow for their use in the future. # Default values are generally black and white. # TODO copy theme from a class attribute. theme ={'normal-foreground':'#000000', 'normal-background':'#ffffff', 'keyword-foreground':'#000000', 'keyword-background':'#ffffff', 'builtin-foreground':'#000000', 'builtin-background':'#ffffff', 'comment-foreground':'#000000', 'comment-background':'#ffffff', 'string-foreground':'#000000', 'string-background':'#ffffff', 'definition-foreground':'#000000', 'definition-background':'#ffffff', 'hilite-foreground':'#000000', 'hilite-background':'gray', 'break-foreground':'#ffffff', 'break-background':'#000000', 'hit-foreground':'#ffffff', 'hit-background':'#000000', 'error-foreground':'#ffffff', 'error-background':'#000000', 'context-foreground':'#000000', 'context-background':'#ffffff', 'linenumber-foreground':'#000000', 'linenumber-background':'#ffffff', #cursor (only foreground can be set) 'cursor-foreground':'#000000', #shell window 'stdout-foreground':'#000000', 'stdout-background':'#ffffff', 'stderr-foreground':'#000000', 'stderr-background':'#ffffff', 'console-foreground':'#000000', 'console-background':'#ffffff', } for element in theme: if not (cfgParser.has_option(themeName, element) or # Skip warning for new elements. element.startswith(('context-', 'linenumber-'))): # Print warning that will return a default color warning = ('\n Warning: config.IdleConf.GetThemeDict' ' -\n problem retrieving theme element %r' '\n from theme %r.\n' ' returning default color: %r' % (element, themeName, theme[element])) _warn(warning, 'highlight', themeName, element) theme[element] = cfgParser.Get( themeName, element, default=theme[element]) return theme def CurrentTheme(self): "Return the name of the currently active text color theme." return self.current_colors_and_keys('Theme') def CurrentKeys(self): """Return the name of the currently active key set.""" return self.current_colors_and_keys('Keys') def current_colors_and_keys(self, section): """Return the currently active name for Theme or Keys section. idlelib.config-main.def ('default') includes these sections [Theme] default= 1 name= IDLE Classic name2= [Keys] default= 1 name= name2= Item 'name2', is used for built-in ('default') themes and keys added after 2015 Oct 1 and 2016 July 1. This kludge is needed because setting 'name' to a builtin not defined in older IDLEs to display multiple error messages or quit. See https://bugs.python.org/issue25313. When default = True, 'name2' takes precedence over 'name', while older IDLEs will just use name. When default = False, 'name2' may still be set, but it is ignored. """ cfgname = 'highlight' if section == 'Theme' else 'keys' default = self.GetOption('main', section, 'default', type='bool', default=True) name = '' if default: name = self.GetOption('main', section, 'name2', default='') if not name: name = self.GetOption('main', section, 'name', default='') if name: source = self.defaultCfg if default else self.userCfg if source[cfgname].has_section(name): return name return "IDLE Classic" if section == 'Theme' else self.default_keys() @staticmethod def default_keys(): if sys.platform[:3] == 'win': return 'IDLE Classic Windows' elif sys.platform == 'darwin': return 'IDLE Classic OSX' else: return 'IDLE Modern Unix' def GetExtensions(self, active_only=True, editor_only=False, shell_only=False): """Return extensions in default and user config-extensions files. If active_only True, only return active (enabled) extensions and optionally only editor or shell extensions. If active_only False, return all extensions. """ extns = self.RemoveKeyBindNames( self.GetSectionList('default', 'extensions')) userExtns = self.RemoveKeyBindNames( self.GetSectionList('user', 'extensions')) for extn in userExtns: if extn not in extns: #user has added own extension extns.append(extn) for extn in ('AutoComplete','CodeContext', 'FormatParagraph','ParenMatch'): extns.remove(extn) # specific exclusions because we are storing config for mainlined old # extensions in config-extensions.def for backward compatibility if active_only: activeExtns = [] for extn in extns: if self.GetOption('extensions', extn, 'enable', default=True, type='bool'): #the extension is enabled if editor_only or shell_only: # TODO both True contradict if editor_only: option = "enable_editor" else: option = "enable_shell" if self.GetOption('extensions', extn,option, default=True, type='bool', warn_on_default=False): activeExtns.append(extn) else: activeExtns.append(extn) return activeExtns else: return extns def RemoveKeyBindNames(self, extnNameList): "Return extnNameList with keybinding section names removed." return [n for n in extnNameList if not n.endswith(('_bindings', '_cfgBindings'))] def GetExtnNameForEvent(self, virtualEvent): """Return the name of the extension binding virtualEvent, or None. virtualEvent - string, name of the virtual event to test for, without the enclosing '<< >>' """ extName = None vEvent = '<<' + virtualEvent + '>>' for extn in self.GetExtensions(active_only=0): for event in self.GetExtensionKeys(extn): if event == vEvent: extName = extn # TODO return here? return extName def GetExtensionKeys(self, extensionName): """Return dict: {configurable extensionName event : active keybinding}. Events come from default config extension_cfgBindings section. Keybindings come from GetCurrentKeySet() active key dict, where previously used bindings are disabled. """ keysName = extensionName + '_cfgBindings' activeKeys = self.GetCurrentKeySet() extKeys = {} if self.defaultCfg['extensions'].has_section(keysName): eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) for eventName in eventNames: event = '<<' + eventName + '>>' binding = activeKeys[event] extKeys[event] = binding return extKeys def __GetRawExtensionKeys(self,extensionName): """Return dict {configurable extensionName event : keybinding list}. Events come from default config extension_cfgBindings section. Keybindings list come from the splitting of GetOption, which tries user config before default config. """ keysName = extensionName+'_cfgBindings' extKeys = {} if self.defaultCfg['extensions'].has_section(keysName): eventNames = self.defaultCfg['extensions'].GetOptionList(keysName) for eventName in eventNames: binding = self.GetOption( 'extensions', keysName, eventName, default='').split() event = '<<' + eventName + '>>' extKeys[event] = binding return extKeys def GetExtensionBindings(self, extensionName): """Return dict {extensionName event : active or defined keybinding}. Augment self.GetExtensionKeys(extensionName) with mapping of non- configurable events (from default config) to GetOption splits, as in self.__GetRawExtensionKeys. """ bindsName = extensionName + '_bindings' extBinds = self.GetExtensionKeys(extensionName) #add the non-configurable bindings if self.defaultCfg['extensions'].has_section(bindsName): eventNames = self.defaultCfg['extensions'].GetOptionList(bindsName) for eventName in eventNames: binding = self.GetOption( 'extensions', bindsName, eventName, default='').split() event = '<<' + eventName + '>>' extBinds[event] = binding return extBinds def GetKeyBinding(self, keySetName, eventStr): """Return the keybinding list for keySetName eventStr. keySetName - name of key binding set (config-keys section). eventStr - virtual event, including brackets, as in '<>'. """ eventName = eventStr[2:-2] #trim off the angle brackets binding = self.GetOption('keys', keySetName, eventName, default='', warn_on_default=False).split() return binding def GetCurrentKeySet(self): "Return CurrentKeys with 'darwin' modifications." result = self.GetKeySet(self.CurrentKeys()) if sys.platform == "darwin": # macOS (OS X) Tk variants do not support the "Alt" # keyboard modifier. Replace it with "Option". # TODO (Ned?): the "Option" modifier does not work properly # for Cocoa Tk and XQuartz Tk so we should not use it # in the default 'OSX' keyset. for k, v in result.items(): v2 = [ x.replace('>' """ return ('<<'+virtualEvent+'>>') in self.GetCoreKeys() # TODO make keyBindins a file or class attribute used for test above # and copied in function below. former_extension_events = { # Those with user-configurable keys. '<>', '<>', '<>', '<>', '<>', '<>', '<>', '<>', '<>', } def GetCoreKeys(self, keySetName=None): """Return dict of core virtual-key keybindings for keySetName. The default keySetName None corresponds to the keyBindings base dict. If keySetName is not None, bindings from the config file(s) are loaded _over_ these defaults, so if there is a problem getting any core binding there will be an 'ultimate last resort fallback' to the CUA-ish bindings defined here. """ keyBindings={ '<>': ['', ''], '<>': ['', ''], '<>': ['', ''], '<>': ['', ''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': ['', ''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': ['', ''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], '<>': [''], } if keySetName: if not (self.userCfg['keys'].has_section(keySetName) or self.defaultCfg['keys'].has_section(keySetName)): warning = ( '\n Warning: config.py - IdleConf.GetCoreKeys -\n' ' key set %r is not defined, using default bindings.' % (keySetName,) ) _warn(warning, 'keys', keySetName) else: for event in keyBindings: binding = self.GetKeyBinding(keySetName, event) if binding: keyBindings[event] = binding # Otherwise return default in keyBindings. elif event not in self.former_extension_events: warning = ( '\n Warning: config.py - IdleConf.GetCoreKeys -\n' ' problem retrieving key binding for event %r\n' ' from key set %r.\n' ' returning default value: %r' % (event, keySetName, keyBindings[event]) ) _warn(warning, 'keys', keySetName, event) return keyBindings def GetExtraHelpSourceList(self, configSet): """Return list of extra help sources from a given configSet. Valid configSets are 'user' or 'default'. Return a list of tuples of the form (menu_item , path_to_help_file , option), or return the empty list. 'option' is the sequence number of the help resource. 'option' values determine the position of the menu items on the Help menu, therefore the returned list must be sorted by 'option'. """ helpSources = [] if configSet == 'user': cfgParser = self.userCfg['main'] elif configSet == 'default': cfgParser = self.defaultCfg['main'] else: raise InvalidConfigSet('Invalid configSet specified') options=cfgParser.GetOptionList('HelpFiles') for option in options: value=cfgParser.Get('HelpFiles', option, default=';') if value.find(';') == -1: #malformed config entry with no ';' menuItem = '' #make these empty helpPath = '' #so value won't be added to list else: #config entry contains ';' as expected value=value.split(';') menuItem=value[0].strip() helpPath=value[1].strip() if menuItem and helpPath: #neither are empty strings helpSources.append( (menuItem,helpPath,option) ) helpSources.sort(key=lambda x: x[2]) return helpSources def GetAllExtraHelpSourcesList(self): """Return a list of the details of all additional help sources. Tuples in the list are those of GetExtraHelpSourceList. """ allHelpSources = (self.GetExtraHelpSourceList('default') + self.GetExtraHelpSourceList('user') ) return allHelpSources def GetFont(self, root, configType, section): """Retrieve a font from configuration (font, font-size, font-bold) Intercept the special value 'TkFixedFont' and substitute the actual font, factoring in some tweaks if needed for appearance sakes. The 'root' parameter can normally be any valid Tkinter widget. Return a tuple (family, size, weight) suitable for passing to tkinter.Font """ family = self.GetOption(configType, section, 'font', default='courier') size = self.GetOption(configType, section, 'font-size', type='int', default='10') bold = self.GetOption(configType, section, 'font-bold', default=0, type='bool') if (family == 'TkFixedFont'): f = Font(name='TkFixedFont', exists=True, root=root) actualFont = Font.actual(f) family = actualFont['family'] size = actualFont['size'] if size <= 0: size = 10 # if font in pixels, ignore actual size bold = actualFont['weight'] == 'bold' return (family, size, 'bold' if bold else 'normal') def LoadCfgFiles(self): "Load all configuration files." for key in self.defaultCfg: self.defaultCfg[key].Load() self.userCfg[key].Load() #same keys def SaveUserCfgFiles(self): "Write all loaded user configuration files to disk." for key in self.userCfg: self.userCfg[key].Save() idleConf = IdleConf() _warned = set() def _warn(msg, *key): key = (msg,) + key if key not in _warned: try: print(msg, file=sys.stderr) except OSError: pass _warned.add(key) class ConfigChanges(dict): """Manage a user's proposed configuration option changes. Names used across multiple methods: page -- one of the 4 top-level dicts representing a .idlerc/config-x.cfg file. config_type -- name of a page. section -- a section within a page/file. option -- name of an option within a section. value -- value for the option. Methods add_option: Add option and value to changes. save_option: Save option and value to config parser. save_all: Save all the changes to the config parser and file. delete_section: If section exists, delete from changes, userCfg, and file. clear: Clear all changes by clearing each page. """ def __init__(self): "Create a page for each configuration file" self.pages = [] # List of unhashable dicts. for config_type in idleConf.config_types: self[config_type] = {} self.pages.append(self[config_type]) def add_option(self, config_type, section, item, value): "Add item/value pair for config_type and section." page = self[config_type] value = str(value) # Make sure we use a string. if section not in page: page[section] = {} page[section][item] = value @staticmethod def save_option(config_type, section, item, value): """Return True if the configuration value was added or changed. Helper for save_all. """ if idleConf.defaultCfg[config_type].has_option(section, item): if idleConf.defaultCfg[config_type].Get(section, item) == value: # The setting equals a default setting, remove it from user cfg. return idleConf.userCfg[config_type].RemoveOption(section, item) # If we got here, set the option. return idleConf.userCfg[config_type].SetOption(section, item, value) def save_all(self): """Save configuration changes to the user config file. Clear self in preparation for additional changes. Return changed for testing. """ idleConf.userCfg['main'].Save() changed = False for config_type in self: cfg_type_changed = False page = self[config_type] for section in page: if section == 'HelpFiles': # Remove it for replacement. idleConf.userCfg['main'].remove_section('HelpFiles') cfg_type_changed = True for item, value in page[section].items(): if self.save_option(config_type, section, item, value): cfg_type_changed = True if cfg_type_changed: idleConf.userCfg[config_type].Save() changed = True for config_type in ['keys', 'highlight']: # Save these even if unchanged! idleConf.userCfg[config_type].Save() self.clear() # ConfigDialog caller must add the following call # self.save_all_changed_extensions() # Uses a different mechanism. return changed def delete_section(self, config_type, section): """Delete a section from self, userCfg, and file. Used to delete custom themes and keysets. """ if section in self[config_type]: del self[config_type][section] configpage = idleConf.userCfg[config_type] configpage.remove_section(section) configpage.Save() def clear(self): """Clear all 4 pages. Called in save_all after saving to idleConf. XXX Mark window *title* when there are changes; unmark here. """ for page in self.pages: page.clear() # TODO Revise test output, write expanded unittest def _dump(): # htest # (not really, but ignore in coverage) from zlib import crc32 line, crc = 0, 0 def sprint(obj): global line, crc txt = str(obj) line += 1 crc = crc32(txt.encode(encoding='utf-8'), crc) print(txt) #print('***', line, crc, '***') # Uncomment for diagnosis. def dumpCfg(cfg): print('\n', cfg, '\n') # Cfg has variable '0xnnnnnnnn' address. for key in sorted(cfg.keys()): sections = cfg[key].sections() sprint(key) sprint(sections) for section in sections: options = cfg[key].options(section) sprint(section) sprint(options) for option in options: sprint(option + ' = ' + cfg[key].Get(section, option)) dumpCfg(idleConf.defaultCfg) dumpCfg(idleConf.userCfg) print('\nlines = ', line, ', crc = ', crc, sep='') if __name__ == '__main__': from unittest import main main('idlelib.idle_test.test_config', verbosity=2, exit=False) # Run revised _dump() as htest? > 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298 1299 1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314 1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349 1350 1351 1352 1353 1354 1355 1356 1357 1358 1359 1360 1361 1362 1363 1364 1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376 1377 1378 1379 1380 1381 1382 1383 1384 1385 1386 1387 1388 1389 1390 1391 1392 1393 1394 1395 1396 1397 1398 1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435
"""
Common tests shared by test_unicode, test_userstring and test_bytes.
"""

import unittest, string, sys, struct
from test import support
from collections import UserList

class Sequence:
    def __init__(self, seq='wxyz'): self.seq = seq
    def __len__(self): return len(self.seq)
    def __getitem__(self, i): return self.seq[i]

class BadSeq1(Sequence):
    def __init__(self): self.seq = [7, 'hello', 123]
    def __str__(self): return '{0} {1} {2}'.format(*self.seq)

class BadSeq2(Sequence):
    def __init__(self): self.seq = ['a', 'b', 'c']
    def __len__(self): return 8

class BaseTest:
    # These tests are for buffers of values (bytes) and not
    # specific to character interpretation, used for bytes objects
    # and various string implementations

    # The type to be tested
    # Change in subclasses to change the behaviour of fixtesttype()
    type2test = None

    # Whether the "contained items" of the container are integers in
    # range(0, 256) (i.e. bytes, bytearray) or strings of length 1
    # (str)
    contains_bytes = False

    # All tests pass their arguments to the testing methods
    # as str objects. fixtesttype() can be used to propagate
    # these arguments to the appropriate type
    def fixtype(self, obj):
        if isinstance(obj, str):
            return self.__class__.type2test(obj)
        elif isinstance(obj, list):
            return [self.fixtype(x) for x in obj]
        elif isinstance(obj, tuple):
            return tuple([self.fixtype(x) for x in obj])
        elif isinstance(obj, dict):
            return dict([
               (self.fixtype(key), self.fixtype(value))
               for (key, value) in obj.items()
            ])
        else:
            return obj

    def test_fixtype(self):
        self.assertIs(type(self.fixtype("123")), self.type2test)

    # check that obj.method(*args) returns result
    def checkequal(self, result, obj, methodname, *args, **kwargs):
        result = self.fixtype(result)
        obj = self.fixtype(obj)
        args = self.fixtype(args)
        kwargs = {k: self.fixtype(v) for k,v in kwargs.items()}
        realresult = getattr(obj, methodname)(*args, **kwargs)
        self.assertEqual(
            result,
            realresult
        )
        # if the original is returned make sure that
        # this doesn't happen with subclasses
        if obj is realresult:
            try:
                class subtype(self.__class__.type2test):
                    pass
            except TypeError:
                pass  # Skip this if we can't subclass
            else:
                obj = subtype(obj)
                realresult = getattr(obj, methodname)(*args)
                self.assertIsNot(obj, realresult)

    # check that obj.method(*args) raises exc
    def checkraises(self, exc, obj, methodname, *args):
        obj = self.fixtype(obj)
        args = self.fixtype(args)
        with self.assertRaises(exc) as cm:
            getattr(obj, methodname)(*args)
        self.assertNotEqual(str(cm.exception), '')

    # call obj.method(*args) without any checks
    def checkcall(self, obj, methodname, *args):
        obj = self.fixtype(obj)
        args = self.fixtype(args)
        getattr(obj, methodname)(*args)

    def test_count(self):
        self.checkequal(3, 'aaa', 'count', 'a')
        self.checkequal(0, 'aaa', 'count', 'b')
        self.checkequal(3, 'aaa', 'count', 'a')
        self.checkequal(0, 'aaa', 'count', 'b')
        self.checkequal(3, 'aaa', 'count', 'a')
        self.checkequal(0, 'aaa', 'count', 'b')
        self.checkequal(0, 'aaa', 'count', 'b')
        self.checkequal(2, 'aaa', 'count', 'a', 1)
        self.checkequal(0, 'aaa', 'count', 'a', 10)
        self.checkequal(1, 'aaa', 'count', 'a', -1)
        self.checkequal(3, 'aaa', 'count', 'a', -10)
        self.checkequal(1, 'aaa', 'count', 'a', 0, 1)
        self.checkequal(3, 'aaa', 'count', 'a', 0, 10)
        self.checkequal(2, 'aaa', 'count', 'a', 0, -1)
        self.checkequal(0, 'aaa', 'count', 'a', 0, -10)
        self.checkequal(3, 'aaa', 'count', '', 1)
        self.checkequal(1, 'aaa', 'count', '', 3)
        self.checkequal(0, 'aaa', 'count', '', 10)
        self.checkequal(2, 'aaa', 'count', '', -1)
        self.checkequal(4, 'aaa', 'count', '', -10)

        self.checkequal(1, '', 'count', '')
        self.checkequal(0, '', 'count', '', 1, 1)
        self.checkequal(0, '', 'count', '', sys.maxsize, 0)

        self.checkequal(0, '', 'count', 'xx')
        self.checkequal(0, '', 'count', 'xx', 1, 1)
        self.checkequal(0, '', 'count', 'xx', sys.maxsize, 0)

        self.checkraises(TypeError, 'hello', 'count')

        if self.contains_bytes:
            self.checkequal(0, 'hello', 'count', 42)
        else:
            self.checkraises(TypeError, 'hello', 'count', 42)

        # For a variety of combinations,
        #    verify that str.count() matches an equivalent function
        #    replacing all occurrences and then differencing the string lengths
        charset = ['', 'a', 'b']
        digits = 7
        base = len(charset)
        teststrings = set()
        for i in range(base ** digits):
            entry = []
            for j in range(digits):
                i, m = divmod(i, base)
                entry.append(charset[m])
            teststrings.add(''.join(entry))
        teststrings = [self.fixtype(ts) for ts in teststrings]
        for i in teststrings:
            n = len(i)
            for j in teststrings:
                r1 = i.count(j)
                if j:
                    r2, rem = divmod(n - len(i.replace(j, self.fixtype(''))),
                                     len(j))
                else:
                    r2, rem = len(i)+1, 0
                if rem or r1 != r2:
                    self.assertEqual(rem, 0, '%s != 0 for %s' % (rem, i))
                    self.assertEqual(r1, r2, '%s != %s for %s' % (r1, r2, i))

    def test_find(self):
        self.checkequal(0, 'abcdefghiabc', 'find', 'abc')
        self.checkequal(9, 'abcdefghiabc', 'find', 'abc', 1)
        self.checkequal(-1, 'abcdefghiabc', 'find', 'def', 4)

        self.checkequal(0, 'abc', 'find', '', 0)
        self.checkequal(3, 'abc', 'find', '', 3)
        self.checkequal(-1, 'abc', 'find', '', 4)

        # to check the ability to pass None as defaults
        self.checkequal( 2, 'rrarrrrrrrrra', 'find', 'a')
        self.checkequal(12, 'rrarrrrrrrrra', 'find', 'a', 4)
        self.checkequal(-1, 'rrarrrrrrrrra', 'find', 'a', 4, 6)
        self.checkequal(12, 'rrarrrrrrrrra', 'find', 'a', 4, None)
        self.checkequal( 2, 'rrarrrrrrrrra', 'find', 'a', None, 6)

        self.checkraises(TypeError, 'hello', 'find')

        if self.contains_bytes:
            self.checkequal(-1, 'hello', 'find', 42)
        else:
            self.checkraises(TypeError, 'hello', 'find', 42)

        self.checkequal(0, '', 'find', '')
        self.checkequal(-1, '', 'find', '', 1, 1)
        self.checkequal(-1, '', 'find', '', sys.maxsize, 0)

        self.checkequal(-1, '', 'find', 'xx')
        self.checkequal(-1, '', 'find', 'xx', 1, 1)
        self.checkequal(-1, '', 'find', 'xx', sys.maxsize, 0)

        # issue 7458
        self.checkequal(-1, 'ab', 'find', 'xxx', sys.maxsize + 1, 0)

        # For a variety of combinations,
        #    verify that str.find() matches __contains__
        #    and that the found substring is really at that location
        charset = ['', 'a', 'b', 'c']
        digits = 5
        base = len(charset)
        teststrings = set()
        for i in range(base ** digits):
            entry = []
            for j in range(digits):
                i, m = divmod(i, base)
                entry.append(charset[m])
            teststrings.add(''.join(entry))
        teststrings = [self.fixtype(ts) for ts in teststrings]
        for i in teststrings:
            for j in teststrings:
                loc = i.find(j)
                r1 = (loc != -1)
                r2 = j in i
                self.assertEqual(r1, r2)
                if loc != -1:
                    self.assertEqual(i[loc:loc+len(j)], j)

    def test_rfind(self):
        self.checkequal(9,  'abcdefghiabc', 'rfind', 'abc')
        self.checkequal(12, 'abcdefghiabc', 'rfind', '')
        self.checkequal(0, 'abcdefghiabc', 'rfind', 'abcd')
        self.checkequal(-1, 'abcdefghiabc', 'rfind', 'abcz')

        self.checkequal(3, 'abc', 'rfind', '', 0)
        self.checkequal(3, 'abc', 'rfind', '', 3)
        self.checkequal(-1, 'abc', 'rfind', '', 4)

        # to check the ability to pass None as defaults
        self.checkequal(12, 'rrarrrrrrrrra', 'rfind', 'a')
        self.checkequal(12, 'rrarrrrrrrrra', 'rfind', 'a', 4)
        self.checkequal(-1, 'rrarrrrrrrrra', 'rfind', 'a', 4, 6)
        self.checkequal(12, 'rrarrrrrrrrra', 'rfind', 'a', 4, None)
        self.checkequal( 2, 'rrarrrrrrrrra', 'rfind', 'a', None, 6)

        self.checkraises(TypeError, 'hello', 'rfind')

        if self.contains_bytes:
            self.checkequal(-1, 'hello', 'rfind', 42)
        else:
            self.checkraises(TypeError, 'hello', 'rfind', 42)

        # For a variety of combinations,
        #    verify that str.rfind() matches __contains__
        #    and that the found substring is really at that location
        charset = ['', 'a', 'b', 'c']
        digits = 5
        base = len(charset)
        teststrings = set()
        for i in range(base ** digits):
            entry = []
            for j in range(digits):
                i, m = divmod(i, base)
                entry.append(charset[m])
            teststrings.add(''.join(entry))
        teststrings = [self.fixtype(ts) for ts in teststrings]
        for i in teststrings:
            for j in teststrings:
                loc = i.rfind(j)
                r1 = (loc != -1)
                r2 = j in i
                self.assertEqual(r1, r2)
                if loc != -1:
                    self.assertEqual(i[loc:loc+len(j)], j)

        # issue 7458
        self.checkequal(-1, 'ab', 'rfind', 'xxx', sys.maxsize + 1, 0)

        # issue #15534
        self.checkequal(0, '<......\u043c...', "rfind", "<")

    def test_index(self):
        self.checkequal(0, 'abcdefghiabc', 'index', '')
        self.checkequal(3, 'abcdefghiabc', 'index', 'def')
        self.checkequal(0, 'abcdefghiabc', 'index', 'abc')
        self.checkequal(9, 'abcdefghiabc', 'index', 'abc', 1)

        self.checkraises(ValueError, 'abcdefghiabc', 'index', 'hib')
        self.checkraises(ValueError, 'abcdefghiab', 'index', 'abc', 1)
        self.checkraises(ValueError, 'abcdefghi', 'index', 'ghi', 8)
        self.checkraises(ValueError, 'abcdefghi', 'index', 'ghi', -1)

        # to check the ability to pass None as defaults
        self.checkequal( 2, 'rrarrrrrrrrra', 'index', 'a')
        self.checkequal(12, 'rrarrrrrrrrra', 'index', 'a', 4)
        self.checkraises(ValueError, 'rrarrrrrrrrra', 'index', 'a', 4, 6)
        self.checkequal(12, 'rrarrrrrrrrra', 'index', 'a', 4, None)
        self.checkequal( 2, 'rrarrrrrrrrra', 'index', 'a', None, 6)

        self.checkraises(TypeError, 'hello', 'index')

        if self.contains_bytes:
            self.checkraises(ValueError, 'hello', 'index', 42)
        else:
            self.checkraises(TypeError, 'hello', 'index', 42)

    def test_rindex(self):
        self.checkequal(12, 'abcdefghiabc', 'rindex', '')
        self.checkequal(3,  'abcdefghiabc', 'rindex', 'def')
        self.checkequal(9,  'abcdefghiabc', 'rindex', 'abc')
        self.checkequal(0,  'abcdefghiabc', 'rindex', 'abc', 0, -1)

        self.checkraises(ValueError, 'abcdefghiabc', 'rindex', 'hib')
        self.checkraises(ValueError, 'defghiabc', 'rindex', 'def', 1)
        self.checkraises(ValueError, 'defghiabc', 'rindex', 'abc', 0, -1)
        self.checkraises(ValueError, 'abcdefghi', 'rindex', 'ghi', 0, 8)
        self.checkraises(ValueError, 'abcdefghi', 'rindex', 'ghi', 0, -1)

        # to check the ability to pass None as defaults
        self.checkequal(12, 'rrarrrrrrrrra', 'rindex', 'a')
        self.checkequal(12, 'rrarrrrrrrrra', 'rindex', 'a', 4)
        self.checkraises(ValueError, 'rrarrrrrrrrra', 'rindex', 'a', 4, 6)
        self.checkequal(12, 'rrarrrrrrrrra', 'rindex', 'a', 4, None)
        self.checkequal( 2, 'rrarrrrrrrrra', 'rindex', 'a', None, 6)

        self.checkraises(TypeError, 'hello', 'rindex')

        if self.contains_bytes:
            self.checkraises(ValueError, 'hello', 'rindex', 42)
        else:
            self.checkraises(TypeError, 'hello', 'rindex', 42)

    def test_lower(self):
        self.checkequal('hello', 'HeLLo', 'lower')
        self.checkequal('hello', 'hello', 'lower')
        self.checkraises(TypeError, 'hello', 'lower', 42)

    def test_upper(self):
        self.checkequal('HELLO', 'HeLLo', 'upper')
        self.checkequal('HELLO', 'HELLO', 'upper')
        self.checkraises(TypeError, 'hello', 'upper', 42)

    def test_expandtabs(self):
        self.checkequal('abc\rab      def\ng       hi', 'abc\rab\tdef\ng\thi',
                        'expandtabs')
        self.checkequal('abc\rab      def\ng       hi', 'abc\rab\tdef\ng\thi',
                        'expandtabs', 8)
        self.checkequal('abc\rab  def\ng   hi', 'abc\rab\tdef\ng\thi',
                        'expandtabs', 4)
        self.checkequal('abc\r\nab      def\ng       hi', 'abc\r\nab\tdef\ng\thi',
                        'expandtabs')
        self.checkequal('abc\r\nab      def\ng       hi', 'abc\r\nab\tdef\ng\thi',
                        'expandtabs', 8)
        self.checkequal('abc\r\nab  def\ng   hi', 'abc\r\nab\tdef\ng\thi',
                        'expandtabs', 4)
        self.checkequal('abc\r\nab\r\ndef\ng\r\nhi', 'abc\r\nab\r\ndef\ng\r\nhi',
                        'expandtabs', 4)
        # check keyword args
        self.checkequal('abc\rab      def\ng       hi', 'abc\rab\tdef\ng\thi',
                        'expandtabs', tabsize=8)
        self.checkequal('abc\rab  def\ng   hi', 'abc\rab\tdef\ng\thi',
                        'expandtabs', tabsize=4)

        self.checkequal('  a\n b', ' \ta\n\tb', 'expandtabs', 1)

        self.checkraises(TypeError, 'hello', 'expandtabs', 42, 42)
        # This test is only valid when sizeof(int) == sizeof(void*) == 4.
        if sys.maxsize < (1 << 32) and struct.calcsize('P') == 4:
            self.checkraises(OverflowError,
                             '\ta\n\tb', 'expandtabs', sys.maxsize)

    def test_split(self):
        # by a char
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'split', '|')
        self.checkequal(['a|b|c|d'], 'a|b|c|d', 'split', '|', 0)
        self.checkequal(['a', 'b|c|d'], 'a|b|c|d', 'split', '|', 1)
        self.checkequal(['a', 'b', 'c|d'], 'a|b|c|d', 'split', '|', 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'split', '|', 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'split', '|', 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'split', '|',
                        sys.maxsize-2)
        self.checkequal(['a|b|c|d'], 'a|b|c|d', 'split', '|', 0)
        self.checkequal(['a', '', 'b||c||d'], 'a||b||c||d', 'split', '|', 2)
        self.checkequal(['abcd'], 'abcd', 'split', '|')
        self.checkequal([''], '', 'split', '|')
        self.checkequal(['endcase ', ''], 'endcase |', 'split', '|')
        self.checkequal(['', ' startcase'], '| startcase', 'split', '|')
        self.checkequal(['', 'bothcase', ''], '|bothcase|', 'split', '|')
        self.checkequal(['a', '', 'b\x00c\x00d'], 'a\x00\x00b\x00c\x00d', 'split', '\x00', 2)

        self.checkequal(['a']*20, ('a|'*20)[:-1], 'split', '|')
        self.checkequal(['a']*15 +['a|a|a|a|a'],
                                   ('a|'*20)[:-1], 'split', '|', 15)

        # by string
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', '//')
        self.checkequal(['a', 'b//c//d'], 'a//b//c//d', 'split', '//', 1)
        self.checkequal(['a', 'b', 'c//d'], 'a//b//c//d', 'split', '//', 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', '//', 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', '//', 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'split', '//',
                        sys.maxsize-10)
        self.checkequal(['a//b//c//d'], 'a//b//c//d', 'split', '//', 0)
        self.checkequal(['a', '', 'b////c////d'], 'a////b////c////d', 'split', '//', 2)
        self.checkequal(['endcase ', ''], 'endcase test', 'split', 'test')
        self.checkequal(['', ' begincase'], 'test begincase', 'split', 'test')
        self.checkequal(['', ' bothcase ', ''], 'test bothcase test',
                        'split', 'test')
        self.checkequal(['a', 'bc'], 'abbbc', 'split', 'bb')
        self.checkequal(['', ''], 'aaa', 'split', 'aaa')
        self.checkequal(['aaa'], 'aaa', 'split', 'aaa', 0)
        self.checkequal(['ab', 'ab'], 'abbaab', 'split', 'ba')
        self.checkequal(['aaaa'], 'aaaa', 'split', 'aab')
        self.checkequal([''], '', 'split', 'aaa')
        self.checkequal(['aa'], 'aa', 'split', 'aaa')
        self.checkequal(['A', 'bobb'], 'Abbobbbobb', 'split', 'bbobb')
        self.checkequal(['A', 'B', ''], 'AbbobbBbbobb', 'split', 'bbobb')

        self.checkequal(['a']*20, ('aBLAH'*20)[:-4], 'split', 'BLAH')
        self.checkequal(['a']*20, ('aBLAH'*20)[:-4], 'split', 'BLAH', 19)
        self.checkequal(['a']*18 + ['aBLAHa'], ('aBLAH'*20)[:-4],
                        'split', 'BLAH', 18)

        # with keyword args
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'split', sep='|')
        self.checkequal(['a', 'b|c|d'],
                        'a|b|c|d', 'split', '|', maxsplit=1)
        self.checkequal(['a', 'b|c|d'],
                        'a|b|c|d', 'split', sep='|', maxsplit=1)
        self.checkequal(['a', 'b|c|d'],
                        'a|b|c|d', 'split', maxsplit=1, sep='|')
        self.checkequal(['a', 'b c d'],
                        'a b c d', 'split', maxsplit=1)

        # argument type
        self.checkraises(TypeError, 'hello', 'split', 42, 42, 42)

        # null case
        self.checkraises(ValueError, 'hello', 'split', '')
        self.checkraises(ValueError, 'hello', 'split', '', 0)

    def test_rsplit(self):
        # by a char
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'rsplit', '|')
        self.checkequal(['a|b|c', 'd'], 'a|b|c|d', 'rsplit', '|', 1)
        self.checkequal(['a|b', 'c', 'd'], 'a|b|c|d', 'rsplit', '|', 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'rsplit', '|', 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'rsplit', '|', 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'rsplit', '|',
                        sys.maxsize-100)
        self.checkequal(['a|b|c|d'], 'a|b|c|d', 'rsplit', '|', 0)
        self.checkequal(['a||b||c', '', 'd'], 'a||b||c||d', 'rsplit', '|', 2)
        self.checkequal(['abcd'], 'abcd', 'rsplit', '|')
        self.checkequal([''], '', 'rsplit', '|')
        self.checkequal(['', ' begincase'], '| begincase', 'rsplit', '|')
        self.checkequal(['endcase ', ''], 'endcase |', 'rsplit', '|')
        self.checkequal(['', 'bothcase', ''], '|bothcase|', 'rsplit', '|')

        self.checkequal(['a\x00\x00b', 'c', 'd'], 'a\x00\x00b\x00c\x00d', 'rsplit', '\x00', 2)

        self.checkequal(['a']*20, ('a|'*20)[:-1], 'rsplit', '|')
        self.checkequal(['a|a|a|a|a']+['a']*15,
                        ('a|'*20)[:-1], 'rsplit', '|', 15)

        # by string
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', '//')
        self.checkequal(['a//b//c', 'd'], 'a//b//c//d', 'rsplit', '//', 1)
        self.checkequal(['a//b', 'c', 'd'], 'a//b//c//d', 'rsplit', '//', 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', '//', 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', '//', 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a//b//c//d', 'rsplit', '//',
                        sys.maxsize-5)
        self.checkequal(['a//b//c//d'], 'a//b//c//d', 'rsplit', '//', 0)
        self.checkequal(['a////b////c', '', 'd'], 'a////b////c////d', 'rsplit', '//', 2)
        self.checkequal(['', ' begincase'], 'test begincase', 'rsplit', 'test')
        self.checkequal(['endcase ', ''], 'endcase test', 'rsplit', 'test')
        self.checkequal(['', ' bothcase ', ''], 'test bothcase test',
                        'rsplit', 'test')
        self.checkequal(['ab', 'c'], 'abbbc', 'rsplit', 'bb')
        self.checkequal(['', ''], 'aaa', 'rsplit', 'aaa')
        self.checkequal(['aaa'], 'aaa', 'rsplit', 'aaa', 0)
        self.checkequal(['ab', 'ab'], 'abbaab', 'rsplit', 'ba')
        self.checkequal(['aaaa'], 'aaaa', 'rsplit', 'aab')
        self.checkequal([''], '', 'rsplit', 'aaa')
        self.checkequal(['aa'], 'aa', 'rsplit', 'aaa')
        self.checkequal(['bbob', 'A'], 'bbobbbobbA', 'rsplit', 'bbobb')
        self.checkequal(['', 'B', 'A'], 'bbobbBbbobbA', 'rsplit', 'bbobb')

        self.checkequal(['a']*20, ('aBLAH'*20)[:-4], 'rsplit', 'BLAH')
        self.checkequal(['a']*20, ('aBLAH'*20)[:-4], 'rsplit', 'BLAH', 19)
        self.checkequal(['aBLAHa'] + ['a']*18, ('aBLAH'*20)[:-4],
                        'rsplit', 'BLAH', 18)

        # with keyword args
        self.checkequal(['a', 'b', 'c', 'd'], 'a|b|c|d', 'rsplit', sep='|')
        self.checkequal(['a|b|c', 'd'],
                        'a|b|c|d', 'rsplit', '|', maxsplit=1)
        self.checkequal(['a|b|c', 'd'],
                        'a|b|c|d', 'rsplit', sep='|', maxsplit=1)
        self.checkequal(['a|b|c', 'd'],
                        'a|b|c|d', 'rsplit', maxsplit=1, sep='|')
        self.checkequal(['a b c', 'd'],
                        'a b c d', 'rsplit', maxsplit=1)

        # argument type
        self.checkraises(TypeError, 'hello', 'rsplit', 42, 42, 42)

        # null case
        self.checkraises(ValueError, 'hello', 'rsplit', '')
        self.checkraises(ValueError, 'hello', 'rsplit', '', 0)

    def test_replace(self):
        EQ = self.checkequal

        # Operations on the empty string
        EQ("", "", "replace", "", "")
        EQ("A", "", "replace", "", "A")
        EQ("", "", "replace", "A", "")
        EQ("", "", "replace", "A", "A")
        EQ("", "", "replace", "", "", 100)
        EQ("A", "", "replace", "", "A", 100)
        EQ("", "", "replace", "", "", sys.maxsize)

        # interleave (from=="", 'to' gets inserted everywhere)
        EQ("A", "A", "replace", "", "")
        EQ("*A*", "A", "replace", "", "*")
        EQ("*1A*1", "A", "replace", "", "*1")
        EQ("*-#A*-#", "A", "replace", "", "*-#")
        EQ("*-A*-A*-", "AA", "replace", "", "*-")
        EQ("*-A*-A*-", "AA", "replace", "", "*-", -1)
        EQ("*-A*-A*-", "AA", "replace", "", "*-", sys.maxsize)
        EQ("*-A*-A*-", "AA", "replace", "", "*-", 4)
        EQ("*-A*-A*-", "AA", "replace", "", "*-", 3)
        EQ("*-A*-A", "AA", "replace", "", "*-", 2)
        EQ("*-AA", "AA", "replace", "", "*-", 1)
        EQ("AA", "AA", "replace", "", "*-", 0)

        # single character deletion (from=="A", to=="")
        EQ("", "A", "replace", "A", "")
        EQ("", "AAA", "replace", "A", "")
        EQ("", "AAA", "replace", "A", "", -1)
        EQ("", "AAA", "replace", "A", "", sys.maxsize)
        EQ("", "AAA", "replace", "A", "", 4)
        EQ("", "AAA", "replace", "A", "", 3)
        EQ("A", "AAA", "replace", "A", "", 2)
        EQ("AA", "AAA", "replace", "A", "", 1)
        EQ("AAA", "AAA", "replace", "A", "", 0)
        EQ("", "AAAAAAAAAA", "replace", "A", "")
        EQ("BCD", "ABACADA", "replace", "A", "")
        EQ("BCD", "ABACADA", "replace", "A", "", -1)
        EQ("BCD", "ABACADA", "replace", "A", "", sys.maxsize)
        EQ("BCD", "ABACADA", "replace", "A", "", 5)
        EQ("BCD", "ABACADA", "replace", "A", "", 4)
        EQ("BCDA", "ABACADA", "replace", "A", "", 3)
        EQ("BCADA", "ABACADA", "replace", "A", "", 2)
        EQ("BACADA", "ABACADA", "replace", "A", "", 1)
        EQ("ABACADA", "ABACADA", "replace", "A", "", 0)
        EQ("BCD", "ABCAD", "replace", "A", "")
        EQ("BCD", "ABCADAA", "replace", "A", "")
        EQ("BCD", "BCD", "replace", "A", "")
        EQ("*************", "*************", "replace", "A", "")
        EQ("^A^", "^"+"A"*1000+"^", "replace", "A", "", 999)

        # substring deletion (from=="the", to=="")
        EQ("", "the", "replace", "the", "")
        EQ("ater", "theater", "replace", "the", "")
        EQ("", "thethe", "replace", "the", "")
        EQ("", "thethethethe", "replace", "the", "")
        EQ("aaaa", "theatheatheathea", "replace", "the", "")
        EQ("that", "that", "replace", "the", "")
        EQ("thaet", "thaet", "replace", "the", "")
        EQ("here and re", "here and there", "replace", "the", "")
        EQ("here and re and re", "here and there and there",
           "replace", "the", "", sys.maxsize)
        EQ("here and re and re", "here and there and there",
           "replace", "the", "", -1)
        EQ("here and re and re", "here and there and there",
           "replace", "the", "", 3)
        EQ("here and re and re", "here and there and there",
           "replace", "the", "", 2)
        EQ("here and re and there", "here and there and there",
           "replace", "the", "", 1)
        EQ("here and there and there", "here and there and there",
           "replace", "the", "", 0)
        EQ("here and re and re", "here and there and there", "replace", "the", "")

        EQ("abc", "abc", "replace", "the", "")
        EQ("abcdefg", "abcdefg", "replace", "the", "")

        # substring deletion (from=="bob", to=="")
        EQ("bob", "bbobob", "replace", "bob", "")
        EQ("bobXbob", "bbobobXbbobob", "replace", "bob", "")
        EQ("aaaaaaa", "aaaaaaabob", "replace", "bob", "")
        EQ("aaaaaaa", "aaaaaaa", "replace", "bob", "")

        # single character replace in place (len(from)==len(to)==1)
        EQ("Who goes there?", "Who goes there?", "replace", "o", "o")
        EQ("WhO gOes there?", "Who goes there?", "replace", "o", "O")
        EQ("WhO gOes there?", "Who goes there?", "replace", "o", "O", sys.maxsize)
        EQ("WhO gOes there?", "Who goes there?", "replace", "o", "O", -1)
        EQ("WhO gOes there?", "Who goes there?", "replace", "o", "O", 3)
        EQ("WhO gOes there?", "Who goes there?", "replace", "o", "O", 2)
        EQ("WhO goes there?", "Who goes there?", "replace", "o", "O", 1)
        EQ("Who goes there?", "Who goes there?", "replace", "o", "O", 0)

        EQ("Who goes there?", "Who goes there?", "replace", "a", "q")
        EQ("who goes there?", "Who goes there?", "replace", "W", "w")
        EQ("wwho goes there?ww", "WWho goes there?WW", "replace", "W", "w")
        EQ("Who goes there!", "Who goes there?", "replace", "?", "!")
        EQ("Who goes there!!", "Who goes there??", "replace", "?", "!")

        EQ("Who goes there?", "Who goes there?", "replace", ".", "!")

        # substring replace in place (len(from)==len(to) > 1)
        EQ("Th** ** a t**sue", "This is a tissue", "replace", "is", "**")
        EQ("Th** ** a t**sue", "This is a tissue", "replace", "is", "**", sys.maxsize)
        EQ("Th** ** a t**sue", "This is a tissue", "replace", "is", "**", -1)
        EQ("Th** ** a t**sue", "This is a tissue", "replace", "is", "**", 4)
        EQ("Th** ** a t**sue", "This is a tissue", "replace", "is", "**", 3)
        EQ("Th** ** a tissue", "This is a tissue", "replace", "is", "**", 2)
        EQ("Th** is a tissue", "This is a tissue", "replace", "is", "**", 1)
        EQ("This is a tissue", "This is a tissue", "replace", "is", "**", 0)
        EQ("cobob", "bobob", "replace", "bob", "cob")
        EQ("cobobXcobocob", "bobobXbobobob", "replace", "bob", "cob")
        EQ("bobob", "bobob", "replace", "bot", "bot")

        # replace single character (len(from)==1, len(to)>1)
        EQ("ReyKKjaviKK", "Reykjavik", "replace", "k", "KK")
        EQ("ReyKKjaviKK", "Reykjavik", "replace", "k", "KK", -1)
        EQ("ReyKKjaviKK", "Reykjavik", "replace", "k", "KK", sys.maxsize)
        EQ("ReyKKjaviKK", "Reykjavik", "replace", "k", "KK", 2)
        EQ("ReyKKjavik", "Reykjavik", "replace", "k", "KK", 1)
        EQ("Reykjavik", "Reykjavik", "replace", "k", "KK", 0)
        EQ("A----B----C----", "A.B.C.", "replace", ".", "----")
        # issue #15534
        EQ('...\u043c......&lt;', '...\u043c......<', "replace", "<", "&lt;")

        EQ("Reykjavik", "Reykjavik", "replace", "q", "KK")

        # replace substring (len(from)>1, len(to)!=len(from))
        EQ("ham, ham, eggs and ham", "spam, spam, eggs and spam",
           "replace", "spam", "ham")
        EQ("ham, ham, eggs and ham", "spam, spam, eggs and spam",
           "replace", "spam", "ham", sys.maxsize)
        EQ("ham, ham, eggs and ham", "spam, spam, eggs and spam",
           "replace", "spam", "ham", -1)
        EQ("ham, ham, eggs and ham", "spam, spam, eggs and spam",
           "replace", "spam", "ham", 4)
        EQ("ham, ham, eggs and ham", "spam, spam, eggs and spam",
           "replace", "spam", "ham", 3)
        EQ("ham, ham, eggs and spam", "spam, spam, eggs and spam",
           "replace", "spam", "ham", 2)
        EQ("ham, spam, eggs and spam", "spam, spam, eggs and spam",
           "replace", "spam", "ham", 1)
        EQ("spam, spam, eggs and spam", "spam, spam, eggs and spam",
           "replace", "spam", "ham", 0)

        EQ("bobob", "bobobob", "replace", "bobob", "bob")
        EQ("bobobXbobob", "bobobobXbobobob", "replace", "bobob", "bob")
        EQ("BOBOBOB", "BOBOBOB", "replace", "bob", "bobby")

        self.checkequal('one@two!three!', 'one!two!three!', 'replace', '!', '@', 1)
        self.checkequal('onetwothree', 'one!two!three!', 'replace', '!', '')
        self.checkequal('one@two@three!', 'one!two!three!', 'replace', '!', '@', 2)
        self.checkequal('one@two@three@', 'one!two!three!', 'replace', '!', '@', 3)
        self.checkequal('one@two@three@', 'one!two!three!', 'replace', '!', '@', 4)
        self.checkequal('one!two!three!', 'one!two!three!', 'replace', '!', '@', 0)
        self.checkequal('one@two@three@', 'one!two!three!', 'replace', '!', '@')
        self.checkequal('one!two!three!', 'one!two!three!', 'replace', 'x', '@')
        self.checkequal('one!two!three!', 'one!two!three!', 'replace', 'x', '@', 2)
        self.checkequal('-a-b-c-', 'abc', 'replace', '', '-')
        self.checkequal('-a-b-c', 'abc', 'replace', '', '-', 3)
        self.checkequal('abc', 'abc', 'replace', '', '-', 0)
        self.checkequal('', '', 'replace', '', '')
        self.checkequal('abc', 'abc', 'replace', 'ab', '--', 0)
        self.checkequal('abc', 'abc', 'replace', 'xy', '--')
        # Next three for SF bug 422088: [OSF1 alpha] string.replace(); died with
        # MemoryError due to empty result (platform malloc issue when requesting
        # 0 bytes).
        self.checkequal('', '123', 'replace', '123', '')
        self.checkequal('', '123123', 'replace', '123', '')
        self.checkequal('x', '123x123', 'replace', '123', '')

        self.checkraises(TypeError, 'hello', 'replace')
        self.checkraises(TypeError, 'hello', 'replace', 42)
        self.checkraises(TypeError, 'hello', 'replace', 42, 'h')
        self.checkraises(TypeError, 'hello', 'replace', 'h', 42)

    @unittest.skipIf(sys.maxsize > (1 << 32) or struct.calcsize('P') != 4,
                     'only applies to 32-bit platforms')
    def test_replace_overflow(self):
        # Check for overflow checking on 32 bit machines
        A2_16 = "A" * (2**16)
        self.checkraises(OverflowError, A2_16, "replace", "", A2_16)
        self.checkraises(OverflowError, A2_16, "replace", "A", A2_16)
        self.checkraises(OverflowError, A2_16, "replace", "AA", A2_16+A2_16)

    def test_removeprefix(self):
        self.checkequal('am', 'spam', 'removeprefix', 'sp')
        self.checkequal('spamspam', 'spamspamspam', 'removeprefix', 'spam')
        self.checkequal('spam', 'spam', 'removeprefix', 'python')
        self.checkequal('spam', 'spam', 'removeprefix', 'spider')
        self.checkequal('spam', 'spam', 'removeprefix', 'spam and eggs')

        self.checkequal('', '', 'removeprefix', '')
        self.checkequal('', '', 'removeprefix', 'abcde')
        self.checkequal('abcde', 'abcde', 'removeprefix', '')
        self.checkequal('', 'abcde', 'removeprefix', 'abcde')

        self.checkraises(TypeError, 'hello', 'removeprefix')
        self.checkraises(TypeError, 'hello', 'removeprefix', 42)
        self.checkraises(TypeError, 'hello', 'removeprefix', 42, 'h')
        self.checkraises(TypeError, 'hello', 'removeprefix', 'h', 42)
        self.checkraises(TypeError, 'hello', 'removeprefix', ("he", "l"))

    def test_removesuffix(self):
        self.checkequal('sp', 'spam', 'removesuffix', 'am')
        self.checkequal('spamspam', 'spamspamspam', 'removesuffix', 'spam')
        self.checkequal('spam', 'spam', 'removesuffix', 'python')
        self.checkequal('spam', 'spam', 'removesuffix', 'blam')
        self.checkequal('spam', 'spam', 'removesuffix', 'eggs and spam')

        self.checkequal('', '', 'removesuffix', '')
        self.checkequal('', '', 'removesuffix', 'abcde')
        self.checkequal('abcde', 'abcde', 'removesuffix', '')
        self.checkequal('', 'abcde', 'removesuffix', 'abcde')

        self.checkraises(TypeError, 'hello', 'removesuffix')
        self.checkraises(TypeError, 'hello', 'removesuffix', 42)
        self.checkraises(TypeError, 'hello', 'removesuffix', 42, 'h')
        self.checkraises(TypeError, 'hello', 'removesuffix', 'h', 42)
        self.checkraises(TypeError, 'hello', 'removesuffix', ("lo", "l"))

    def test_capitalize(self):
        self.checkequal(' hello ', ' hello ', 'capitalize')
        self.checkequal('Hello ', 'Hello ','capitalize')
        self.checkequal('Hello ', 'hello ','capitalize')
        self.checkequal('Aaaa', 'aaaa', 'capitalize')
        self.checkequal('Aaaa', 'AaAa', 'capitalize')

        self.checkraises(TypeError, 'hello', 'capitalize', 42)

    def test_additional_split(self):
        self.checkequal(['this', 'is', 'the', 'split', 'function'],
            'this is the split function', 'split')

        # by whitespace
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d ', 'split')
        self.checkequal(['a', 'b c d'], 'a b c d', 'split', None, 1)
        self.checkequal(['a', 'b', 'c d'], 'a b c d', 'split', None, 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'split', None, 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'split', None, 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'split', None,
                        sys.maxsize-1)
        self.checkequal(['a b c d'], 'a b c d', 'split', None, 0)
        self.checkequal(['a b c d'], '  a b c d', 'split', None, 0)
        self.checkequal(['a', 'b', 'c  d'], 'a  b  c  d', 'split', None, 2)

        self.checkequal([], '         ', 'split')
        self.checkequal(['a'], '  a    ', 'split')
        self.checkequal(['a', 'b'], '  a    b   ', 'split')
        self.checkequal(['a', 'b   '], '  a    b   ', 'split', None, 1)
        self.checkequal(['a    b   c   '], '  a    b   c   ', 'split', None, 0)
        self.checkequal(['a', 'b   c   '], '  a    b   c   ', 'split', None, 1)
        self.checkequal(['a', 'b', 'c   '], '  a    b   c   ', 'split', None, 2)
        self.checkequal(['a', 'b', 'c'], '  a    b   c   ', 'split', None, 3)
        self.checkequal(['a', 'b'], '\n\ta \t\r b \v ', 'split')
        aaa = ' a '*20
        self.checkequal(['a']*20, aaa, 'split')
        self.checkequal(['a'] + [aaa[4:]], aaa, 'split', None, 1)
        self.checkequal(['a']*19 + ['a '], aaa, 'split', None, 19)

        for b in ('arf\tbarf', 'arf\nbarf', 'arf\rbarf',
                  'arf\fbarf', 'arf\vbarf'):
            self.checkequal(['arf', 'barf'], b, 'split')
            self.checkequal(['arf', 'barf'], b, 'split', None)
            self.checkequal(['arf', 'barf'], b, 'split', None, 2)

    def test_additional_rsplit(self):
        self.checkequal(['this', 'is', 'the', 'rsplit', 'function'],
                         'this is the rsplit function', 'rsplit')

        # by whitespace
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d ', 'rsplit')
        self.checkequal(['a b c', 'd'], 'a b c d', 'rsplit', None, 1)
        self.checkequal(['a b', 'c', 'd'], 'a b c d', 'rsplit', None, 2)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'rsplit', None, 3)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'rsplit', None, 4)
        self.checkequal(['a', 'b', 'c', 'd'], 'a b c d', 'rsplit', None,
                        sys.maxsize-20)
        self.checkequal(['a b c d'], 'a b c d', 'rsplit', None, 0)
        self.checkequal(['a b c d'], 'a b c d  ', 'rsplit', None, 0)
        self.checkequal(['a  b', 'c', 'd'], 'a  b  c  d', 'rsplit', None, 2)

        self.checkequal([], '         ', 'rsplit')
        self.checkequal(['a'], '  a    ', 'rsplit')
        self.checkequal(['a', 'b'], '  a    b   ', 'rsplit')
        self.checkequal(['  a', 'b'], '  a    b   ', 'rsplit', None, 1)
        self.checkequal(['  a    b   c'], '  a    b   c   ', 'rsplit',
                        None, 0)
        self.checkequal(['  a    b','c'], '  a    b   c   ', 'rsplit',
                        None, 1)
        self.checkequal(['  a', 'b', 'c'], '  a    b   c   ', 'rsplit',
                        None, 2)
        self.checkequal(['a', 'b', 'c'], '  a    b   c   ', 'rsplit',
                        None, 3)
        self.checkequal(['a', 'b'], '\n\ta \t\r b \v ', 'rsplit', None, 88)
        aaa = ' a '*20
        self.checkequal(['a']*20, aaa, 'rsplit')
        self.checkequal([aaa[:-4]] + ['a'], aaa, 'rsplit', None, 1)
        self.checkequal([' a  a'] + ['a']*18, aaa, 'rsplit', None, 18)

        for b in ('arf\tbarf', 'arf\nbarf', 'arf\rbarf',
                  'arf\fbarf', 'arf\vbarf'):
            self.checkequal(['arf', 'barf'], b, 'rsplit')
            self.checkequal(['arf', 'barf'], b, 'rsplit', None)
            self.checkequal(['arf', 'barf'], b, 'rsplit', None, 2)

    def test_strip_whitespace(self):
        self.checkequal('hello', '   hello   ', 'strip')
        self.checkequal('hello   ', '   hello   ', 'lstrip')
        self.checkequal('   hello', '   hello   ', 'rstrip')
        self.checkequal('hello', 'hello', 'strip')

        b = ' \t\n\r\f\vabc \t\n\r\f\v'
        self.checkequal('abc', b, 'strip')
        self.checkequal('abc \t\n\r\f\v', b, 'lstrip')
        self.checkequal(' \t\n\r\f\vabc', b, 'rstrip')

        # strip/lstrip/rstrip with None arg
        self.checkequal('hello', '   hello   ', 'strip', None)
        self.checkequal('hello   ', '   hello   ', 'lstrip', None)
        self.checkequal('   hello', '   hello   ', 'rstrip', None)
        self.checkequal('hello', 'hello', 'strip', None)

    def test_strip(self):
        # strip/lstrip/rstrip with str arg
        self.checkequal('hello', 'xyzzyhelloxyzzy', 'strip', 'xyz')
        self.checkequal('helloxyzzy', 'xyzzyhelloxyzzy', 'lstrip', 'xyz')
        self.checkequal('xyzzyhello', 'xyzzyhelloxyzzy', 'rstrip', 'xyz')
        self.checkequal('hello', 'hello', 'strip', 'xyz')
        self.checkequal('', 'mississippi', 'strip', 'mississippi')

        # only trim the start and end; does not strip internal characters
        self.checkequal('mississipp', 'mississippi', 'strip', 'i')

        self.checkraises(TypeError, 'hello', 'strip', 42, 42)
        self.checkraises(TypeError, 'hello', 'lstrip', 42, 42)
        self.checkraises(TypeError, 'hello', 'rstrip', 42, 42)

    def test_ljust(self):
        self.checkequal('abc       ', 'abc', 'ljust', 10)
        self.checkequal('abc   ', 'abc', 'ljust', 6)
        self.checkequal('abc', 'abc', 'ljust', 3)
        self.checkequal('abc', 'abc', 'ljust', 2)
        self.checkequal('abc*******', 'abc', 'ljust', 10, '*')
        self.checkraises(TypeError, 'abc', 'ljust')

    def test_rjust(self):
        self.checkequal('       abc', 'abc', 'rjust', 10)
        self.checkequal('   abc', 'abc', 'rjust', 6)
        self.checkequal('abc', 'abc', 'rjust', 3)
        self.checkequal('abc', 'abc', 'rjust', 2)
        self.checkequal('*******abc', 'abc', 'rjust', 10, '*')
        self.checkraises(TypeError, 'abc', 'rjust')

    def test_center(self):
        self.checkequal('   abc    ', 'abc', 'center', 10)
        self.checkequal(' abc  ', 'abc', 'center', 6)
        self.checkequal('abc', 'abc', 'center', 3)
        self.checkequal('abc', 'abc', 'center', 2)
        self.checkequal('***abc****', 'abc', 'center', 10, '*')
        self.checkraises(TypeError, 'abc', 'center')

    def test_swapcase(self):
        self.checkequal('hEllO CoMPuTErS', 'HeLLo cOmpUteRs', 'swapcase')

        self.checkraises(TypeError, 'hello', 'swapcase', 42)

    def test_zfill(self):
        self.checkequal('123', '123', 'zfill', 2)
        self.checkequal('123', '123', 'zfill', 3)
        self.checkequal('0123', '123', 'zfill', 4)
        self.checkequal('+123', '+123', 'zfill', 3)
        self.checkequal('+123', '+123', 'zfill', 4)
        self.checkequal('+0123', '+123', 'zfill', 5)
        self.checkequal('-123', '-123', 'zfill', 3)
        self.checkequal('-123', '-123', 'zfill', 4)
        self.checkequal('-0123', '-123', 'zfill', 5)
        self.checkequal('000', '', 'zfill', 3)
        self.checkequal('34', '34', 'zfill', 1)
        self.checkequal('0034', '34', 'zfill', 4)

        self.checkraises(TypeError, '123', 'zfill')

    def test_islower(self):
        self.checkequal(False, '', 'islower')
        self.checkequal(True, 'a', 'islower')
        self.checkequal(False, 'A', 'islower')
        self.checkequal(False, '\n', 'islower')
        self.checkequal(True, 'abc', 'islower')
        self.checkequal(False, 'aBc', 'islower')
        self.checkequal(True, 'abc\n', 'islower')
        self.checkraises(TypeError, 'abc', 'islower', 42)

    def test_isupper(self):
        self.checkequal(False, '', 'isupper')
        self.checkequal(False, 'a', 'isupper')
        self.checkequal(True, 'A', 'isupper')
        self.checkequal(False, '\n', 'isupper')
        self.checkequal(True, 'ABC', 'isupper')
        self.checkequal(False, 'AbC', 'isupper')
        self.checkequal(True, 'ABC\n', 'isupper')
        self.checkraises(TypeError, 'abc', 'isupper', 42)

    def test_istitle(self):
        self.checkequal(False, '', 'istitle')
        self.checkequal(False, 'a', 'istitle')
        self.checkequal(True, 'A', 'istitle')
        self.checkequal(False, '\n', 'istitle')
        self.checkequal(True, 'A Titlecased Line', 'istitle')
        self.checkequal(True, 'A\nTitlecased Line', 'istitle')
        self.checkequal(True, 'A Titlecased, Line', 'istitle')
        self.checkequal(False, 'Not a capitalized String', 'istitle')
        self.checkequal(False, 'Not\ta Titlecase String', 'istitle')
        self.checkequal(False, 'Not--a Titlecase String', 'istitle')
        self.checkequal(False, 'NOT', 'istitle')
        self.checkraises(TypeError, 'abc', 'istitle', 42)

    def test_isspace(self):
        self.checkequal(False, '', 'isspace')
        self.checkequal(False, 'a', 'isspace')
        self.checkequal(True, ' ', 'isspace')
        self.checkequal(True, '\t', 'isspace')
        self.checkequal(True, '\r', 'isspace')
        self.checkequal(True, '\n', 'isspace')
        self.checkequal(True, ' \t\r\n', 'isspace')
        self.checkequal(False, ' \t\r\na', 'isspace')
        self.checkraises(TypeError, 'abc', 'isspace', 42)

    def test_isalpha(self):
        self.checkequal(False, '', 'isalpha')
        self.checkequal(True, 'a', 'isalpha')
        self.checkequal(True, 'A', 'isalpha')
        self.checkequal(False, '\n', 'isalpha')
        self.checkequal(True, 'abc', 'isalpha')
        self.checkequal(False, 'aBc123', 'isalpha')
        self.checkequal(False, 'abc\n', 'isalpha')
        self.checkraises(TypeError, 'abc', 'isalpha', 42)

    def test_isalnum(self):
        self.checkequal(False, '', 'isalnum')
        self.checkequal(True, 'a', 'isalnum')
        self.checkequal(True, 'A', 'isalnum')
        self.checkequal(False, '\n', 'isalnum')
        self.checkequal(True, '123abc456', 'isalnum')
        self.checkequal(True, 'a1b3c', 'isalnum')
        self.checkequal(False, 'aBc000 ', 'isalnum')
        self.checkequal(False, 'abc\n', 'isalnum')
        self.checkraises(TypeError, 'abc', 'isalnum', 42)

    def test_isascii(self):
        self.checkequal(True, '', 'isascii')
        self.checkequal(True, '\x00', 'isascii')
        self.checkequal(True, '\x7f', 'isascii')
        self.checkequal(True, '\x00\x7f', 'isascii')
        self.checkequal(False, '\x80', 'isascii')
        self.checkequal(False, '\xe9', 'isascii')
        # bytes.isascii() and bytearray.isascii() has optimization which
        # check 4 or 8 bytes at once.  So check some alignments.
        for p in range(8):
            self.checkequal(True, ' '*p + '\x7f', 'isascii')
            self.checkequal(False, ' '*p + '\x80', 'isascii')
            self.checkequal(True, ' '*p + '\x7f' + ' '*8, 'isascii')
            self.checkequal(False, ' '*p + '\x80' + ' '*8, 'isascii')

    def test_isdigit(self):
        self.checkequal(False, '', 'isdigit')
        self.checkequal(False, 'a', 'isdigit')
        self.checkequal(True, '0', 'isdigit')
        self.checkequal(True, '0123456789', 'isdigit')
        self.checkequal(False, '0123456789a', 'isdigit')

        self.checkraises(TypeError, 'abc', 'isdigit', 42)

    def test_title(self):
        self.checkequal(' Hello ', ' hello ', 'title')
        self.checkequal('Hello ', 'hello ', 'title')
        self.checkequal('Hello ', 'Hello ', 'title')
        self.checkequal('Format This As Title String', "fOrMaT thIs aS titLe String", 'title')
        self.checkequal('Format,This-As*Title;String', "fOrMaT,thIs-aS*titLe;String", 'title', )
        self.checkequal('Getint', "getInt", 'title')
        self.checkraises(TypeError, 'hello', 'title', 42)

    def test_splitlines(self):
        self.checkequal(['abc', 'def', '', 'ghi'], "abc\ndef\n\rghi", 'splitlines')
        self.checkequal(['abc', 'def', '', 'ghi'], "abc\ndef\n\r\nghi", 'splitlines')
        self.checkequal(['abc', 'def', 'ghi'], "abc\ndef\r\nghi", 'splitlines')
        self.checkequal(['abc', 'def', 'ghi'], "abc\ndef\r\nghi\n", 'splitlines')
        self.checkequal(['abc', 'def', 'ghi', ''], "abc\ndef\r\nghi\n\r", 'splitlines')
        self.checkequal(['', 'abc', 'def', 'ghi', ''], "\nabc\ndef\r\nghi\n\r", 'splitlines')
        self.checkequal(['', 'abc', 'def', 'ghi', ''],
                        "\nabc\ndef\r\nghi\n\r", 'splitlines', False)
        self.checkequal(['\n', 'abc\n', 'def\r\n', 'ghi\n', '\r'],
                        "\nabc\ndef\r\nghi\n\r", 'splitlines', True)
        self.checkequal(['', 'abc', 'def', 'ghi', ''], "\nabc\ndef\r\nghi\n\r",
                        'splitlines', keepends=False)
        self.checkequal(['\n', 'abc\n', 'def\r\n', 'ghi\n', '\r'],
                        "\nabc\ndef\r\nghi\n\r", 'splitlines', keepends=True)

        self.checkraises(TypeError, 'abc', 'splitlines', 42, 42)


class CommonTest(BaseTest):
    # This testcase contains tests that can be used in all
    # stringlike classes. Currently this is str and UserString.

    def test_hash(self):
        # SF bug 1054139:  += optimization was not invalidating cached hash value
        a = self.type2test('DNSSEC')
        b = self.type2test('')
        for c in a:
            b += c
            hash(b)
        self.assertEqual(hash(a), hash(b))

    def test_capitalize_nonascii(self):
        # check that titlecased chars are lowered correctly
        # \u1ffc is the titlecased char
        self.checkequal('\u1ffc\u1ff3\u1ff3\u1ff3',
                        '\u1ff3\u1ff3\u1ffc\u1ffc', 'capitalize')
        # check with cased non-letter chars
        self.checkequal('\u24c5\u24e8\u24e3\u24d7\u24de\u24dd',
                        '\u24c5\u24ce\u24c9\u24bd\u24c4\u24c3', 'capitalize')
        self.checkequal('\u24c5\u24e8\u24e3\u24d7\u24de\u24dd',
                        '\u24df\u24e8\u24e3\u24d7\u24de\u24dd', 'capitalize')
        self.checkequal('\u2160\u2171\u2172',
                        '\u2160\u2161\u2162', 'capitalize')
        self.checkequal('\u2160\u2171\u2172',
                        '\u2170\u2171\u2172', 'capitalize')
        # check with Ll chars with no upper - nothing changes here
        self.checkequal('\u019b\u1d00\u1d86\u0221\u1fb7',
                        '\u019b\u1d00\u1d86\u0221\u1fb7', 'capitalize')


class MixinStrUnicodeUserStringTest:
    # additional tests that only work for
    # stringlike objects, i.e. str, UserString

    def test_startswith(self):
        self.checkequal(True, 'hello', 'startswith', 'he')
        self.checkequal(True, 'hello', 'startswith', 'hello')
        self.checkequal(False, 'hello', 'startswith', 'hello world')
        self.checkequal(True, 'hello', 'startswith', '')
        self.checkequal(False, 'hello', 'startswith', 'ello')
        self.checkequal(True, 'hello', 'startswith', 'ello', 1)
        self.checkequal(True, 'hello', 'startswith', 'o', 4)
        self.checkequal(False, 'hello', 'startswith', 'o', 5)
        self.checkequal(True, 'hello', 'startswith', '', 5)
        self.checkequal(False, 'hello', 'startswith', 'lo', 6)
        self.checkequal(True, 'helloworld', 'startswith', 'lowo', 3)
        self.checkequal(True, 'helloworld', 'startswith', 'lowo', 3, 7)
        self.checkequal(False, 'helloworld', 'startswith', 'lowo', 3, 6)
        self.checkequal(True, '', 'startswith', '', 0, 1)
        self.checkequal(True, '', 'startswith', '', 0, 0)
        self.checkequal(False, '', 'startswith', '', 1, 0)

        # test negative indices
        self.checkequal(True, 'hello', 'startswith', 'he', 0, -1)
        self.checkequal(True, 'hello', 'startswith', 'he', -53, -1)
        self.checkequal(False, 'hello', 'startswith', 'hello', 0, -1)
        self.checkequal(False, 'hello', 'startswith', 'hello world', -1, -10)
        self.checkequal(False, 'hello', 'startswith', 'ello', -5)
        self.checkequal(True, 'hello', 'startswith', 'ello', -4)
        self.checkequal(False, 'hello', 'startswith', 'o', -2)
        self.checkequal(True, 'hello', 'startswith', 'o', -1)
        self.checkequal(True, 'hello', 'startswith', '', -3, -3)
        self.checkequal(False, 'hello', 'startswith', 'lo', -9)

        self.checkraises(TypeError, 'hello', 'startswith')
        self.checkraises(TypeError, 'hello', 'startswith', 42)

        # test tuple arguments
        self.checkequal(True, 'hello', 'startswith', ('he', 'ha'))
        self.checkequal(False, 'hello', 'startswith', ('lo', 'llo'))
        self.checkequal(True, 'hello', 'startswith', ('hellox', 'hello'))
        self.checkequal(False, 'hello', 'startswith', ())
        self.checkequal(True, 'helloworld', 'startswith', ('hellowo',
                                                           'rld', 'lowo'), 3)
        self.checkequal(False, 'helloworld', 'startswith', ('hellowo', 'ello',
                                                            'rld'), 3)
        self.checkequal(True, 'hello', 'startswith', ('lo', 'he'), 0, -1)
        self.checkequal(False, 'hello', 'startswith', ('he', 'hel'), 0, 1)
        self.checkequal(True, 'hello', 'startswith', ('he', 'hel'), 0, 2)

        self.checkraises(TypeError, 'hello', 'startswith', (42,))

    def test_endswith(self):
        self.checkequal(True, 'hello', 'endswith', 'lo')
        self.checkequal(False, 'hello', 'endswith', 'he')
        self.checkequal(True, 'hello', 'endswith', '')
        self.checkequal(False, 'hello', 'endswith', 'hello world')
        self.checkequal(False, 'helloworld', 'endswith', 'worl')
        self.checkequal(True, 'helloworld', 'endswith', 'worl', 3, 9)
        self.checkequal(True, 'helloworld', 'endswith', 'world', 3, 12)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', 1, 7)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', 2, 7)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', 3, 7)
        self.checkequal(False, 'helloworld', 'endswith', 'lowo', 4, 7)
        self.checkequal(False, 'helloworld', 'endswith', 'lowo', 3, 8)
        self.checkequal(False, 'ab', 'endswith', 'ab', 0, 1)
        self.checkequal(False, 'ab', 'endswith', 'ab', 0, 0)
        self.checkequal(True, '', 'endswith', '', 0, 1)
        self.checkequal(True, '', 'endswith', '', 0, 0)
        self.checkequal(False, '', 'endswith', '', 1, 0)

        # test negative indices
        self.checkequal(True, 'hello', 'endswith', 'lo', -2)
        self.checkequal(False, 'hello', 'endswith', 'he', -2)
        self.checkequal(True, 'hello', 'endswith', '', -3, -3)
        self.checkequal(False, 'hello', 'endswith', 'hello world', -10, -2)
        self.checkequal(False, 'helloworld', 'endswith', 'worl', -6)
        self.checkequal(True, 'helloworld', 'endswith', 'worl', -5, -1)
        self.checkequal(True, 'helloworld', 'endswith', 'worl', -5, 9)
        self.checkequal(True, 'helloworld', 'endswith', 'world', -7, 12)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', -99, -3)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', -8, -3)
        self.checkequal(True, 'helloworld', 'endswith', 'lowo', -7, -3)
        self.checkequal(False, 'helloworld', 'endswith', 'lowo', 3, -4)
        self.checkequal(False, 'helloworld', 'endswith', 'lowo', -8, -2)

        self.checkraises(TypeError, 'hello', 'endswith')
        self.checkraises(TypeError, 'hello', 'endswith', 42)

        # test tuple arguments
        self.checkequal(False, 'hello', 'endswith', ('he', 'ha'))
        self.checkequal(True, 'hello', 'endswith', ('lo', 'llo'))
        self.checkequal(True, 'hello', 'endswith', ('hellox', 'hello'))
        self.checkequal(False, 'hello', 'endswith', ())
        self.checkequal(True, 'helloworld', 'endswith', ('hellowo',
                                                           'rld', 'lowo'), 3)
        self.checkequal(False, 'helloworld', 'endswith', ('hellowo', 'ello',
                                                            'rld'), 3, -1)
        self.checkequal(True, 'hello', 'endswith', ('hell', 'ell'), 0, -1)
        self.checkequal(False, 'hello', 'endswith', ('he', 'hel'), 0, 1)
        self.checkequal(True, 'hello', 'endswith', ('he', 'hell'), 0, 4)

        self.checkraises(TypeError, 'hello', 'endswith', (42,))

    def test___contains__(self):
        self.checkequal(True, '', '__contains__', '')
        self.checkequal(True, 'abc', '__contains__', '')
        self.checkequal(False, 'abc', '__contains__', '\0')
        self.checkequal(True, '\0abc', '__contains__', '\0')
        self.checkequal(True, 'abc\0', '__contains__', '\0')
        self.checkequal(True, '\0abc', '__contains__', 'a')
        self.checkequal(True, 'asdf', '__contains__', 'asdf')
        self.checkequal(False, 'asd', '__contains__', 'asdf')
        self.checkequal(False, '', '__contains__', 'asdf')

    def test_subscript(self):
        self.checkequal('a', 'abc', '__getitem__', 0)
        self.checkequal('c', 'abc', '__getitem__', -1)
        self.checkequal('a', 'abc', '__getitem__', 0)
        self.checkequal('abc', 'abc', '__getitem__', slice(0, 3))
        self.checkequal('abc', 'abc', '__getitem__', slice(0, 1000))
        self.checkequal('a', 'abc', '__getitem__', slice(0, 1))
        self.checkequal('', 'abc', '__getitem__', slice(0, 0))

        self.checkraises(TypeError, 'abc', '__getitem__', 'def')

    def test_slice(self):
        self.checkequal('abc', 'abc', '__getitem__', slice(0, 1000))
        self.checkequal('abc', 'abc', '__getitem__', slice(0, 3))
        self.checkequal('ab', 'abc', '__getitem__', slice(0, 2))
        self.checkequal('bc', 'abc', '__getitem__', slice(1, 3))
        self.checkequal('b', 'abc', '__getitem__', slice(1, 2))
        self.checkequal('', 'abc', '__getitem__', slice(2, 2))
        self.checkequal('', 'abc', '__getitem__', slice(1000, 1000))
        self.checkequal('', 'abc', '__getitem__', slice(2000, 1000))
        self.checkequal('', 'abc', '__getitem__', slice(2, 1))

        self.checkraises(TypeError, 'abc', '__getitem__', 'def')

    def test_extended_getslice(self):
        # Test extended slicing by comparing with list slicing.
        s = string.ascii_letters + string.digits
        indices = (0, None, 1, 3, 41, sys.maxsize, -1, -2, -37)
        for start in indices:
            for stop in indices:
                # Skip step 0 (invalid)
                for step in indices[1:]:
                    L = list(s)[start:stop:step]
                    self.checkequal("".join(L), s, '__getitem__',
                                    slice(start, stop, step))

    def test_mul(self):
        self.checkequal('', 'abc', '__mul__', -1)
        self.checkequal('', 'abc', '__mul__', 0)
        self.checkequal('abc', 'abc', '__mul__', 1)
        self.checkequal('abcabcabc', 'abc', '__mul__', 3)
        self.checkraises(TypeError, 'abc', '__mul__')
        self.checkraises(TypeError, 'abc', '__mul__', '')
        # XXX: on a 64-bit system, this doesn't raise an overflow error,
        # but either raises a MemoryError, or succeeds (if you have 54TiB)
        #self.checkraises(OverflowError, 10000*'abc', '__mul__', 2000000000)

    def test_join(self):
        # join now works with any sequence type
        # moved here, because the argument order is
        # different in string.join
        self.checkequal('a b c d', ' ', 'join', ['a', 'b', 'c', 'd'])
        self.checkequal('abcd', '', 'join', ('a', 'b', 'c', 'd'))
        self.checkequal('bd', '', 'join', ('', 'b', '', 'd'))
        self.checkequal('ac', '', 'join', ('a', '', 'c', ''))
        self.checkequal('w x y z', ' ', 'join', Sequence())
        self.checkequal('abc', 'a', 'join', ('abc',))
        self.checkequal('z', 'a', 'join', UserList(['z']))
        self.checkequal('a.b.c', '.', 'join', ['a', 'b', 'c'])
        self.assertRaises(TypeError, '.'.join, ['a', 'b', 3])
        for i in [5, 25, 125]:
            self.checkequal(((('a' * i) + '-') * i)[:-1], '-', 'join',
                 ['a' * i] * i)
            self.checkequal(((('a' * i) + '-') * i)[:-1], '-', 'join',
                 ('a' * i,) * i)

        #self.checkequal(str(BadSeq1()), ' ', 'join', BadSeq1())
        self.checkequal('a b c', ' ', 'join', BadSeq2())

        self.checkraises(TypeError, ' ', 'join')
        self.checkraises(TypeError, ' ', 'join', None)
        self.checkraises(TypeError, ' ', 'join', 7)
        self.checkraises(TypeError, ' ', 'join', [1, 2, bytes()])
        try:
            def f():
                yield 4 + ""
            self.fixtype(' ').join(f())
        except TypeError as e:
            if '+' not in str(e):
                self.fail('join() ate exception message')
        else:
            self.fail('exception not raised')

    def test_formatting(self):
        self.checkequal('+hello+', '+%s+', '__mod__', 'hello')
        self.checkequal('+10+', '+%d+', '__mod__', 10)
        self.checkequal('a', "%c", '__mod__', "a")
        self.checkequal('a', "%c", '__mod__', "a")
        self.checkequal('"', "%c", '__mod__', 34)
        self.checkequal('$', "%c", '__mod__', 36)
        self.checkequal('10', "%d", '__mod__', 10)
        self.checkequal('\x7f', "%c", '__mod__', 0x7f)

        for ordinal in (-100, 0x200000):
            # unicode raises ValueError, str raises OverflowError
            self.checkraises((ValueError, OverflowError), '%c', '__mod__', ordinal)

        longvalue = sys.maxsize + 10
        slongvalue = str(longvalue)
        self.checkequal(' 42', '%3ld', '__mod__', 42)
        self.checkequal('42', '%d', '__mod__', 42.0)
        self.checkequal(slongvalue, '%d', '__mod__', longvalue)
        self.checkcall('%d', '__mod__', float(longvalue))
        self.checkequal('0042.00', '%07.2f', '__mod__', 42)
        self.checkequal('0042.00', '%07.2F', '__mod__', 42)

        self.checkraises(TypeError, 'abc', '__mod__')
        self.checkraises(TypeError, '%(foo)s', '__mod__', 42)
        self.checkraises(TypeError, '%s%s', '__mod__', (42,))
        self.checkraises(TypeError, '%c', '__mod__', (None,))
        self.checkraises(ValueError, '%(foo', '__mod__', {})
        self.checkraises(TypeError, '%(foo)s %(bar)s', '__mod__', ('foo', 42))
        self.checkraises(TypeError, '%d', '__mod__', "42") # not numeric
        self.checkraises(TypeError, '%d', '__mod__', (42+0j)) # no int conversion provided

        # argument names with properly nested brackets are supported
        self.checkequal('bar', '%((foo))s', '__mod__', {'(foo)': 'bar'})

        # 100 is a magic number in PyUnicode_Format, this forces a resize
        self.checkequal(103*'a'+'x', '%sx', '__mod__', 103*'a')

        self.checkraises(TypeError, '%*s', '__mod__', ('foo', 'bar'))
        self.checkraises(TypeError, '%10.*f', '__mod__', ('foo', 42.))
        self.checkraises(ValueError, '%10', '__mod__', (42,))

        # Outrageously large width or precision should raise ValueError.
        self.checkraises(ValueError, '%%%df' % (2**64), '__mod__', (3.2))
        self.checkraises(ValueError, '%%.%df' % (2**64), '__mod__', (3.2))
        self.checkraises(OverflowError, '%*s', '__mod__',
                         (sys.maxsize + 1, ''))
        self.checkraises(OverflowError, '%.*f', '__mod__',
                         (sys.maxsize + 1, 1. / 7))

        class X(object): pass
        self.checkraises(TypeError, 'abc', '__mod__', X())

    @support.cpython_only
    def test_formatting_c_limits(self):
        from _testcapi import PY_SSIZE_T_MAX, INT_MAX, UINT_MAX
        SIZE_MAX = (1 << (PY_SSIZE_T_MAX.bit_length() + 1)) - 1
        self.checkraises(OverflowError, '%*s', '__mod__',
                         (PY_SSIZE_T_MAX + 1, ''))
        self.checkraises(OverflowError, '%.*f', '__mod__',
                         (INT_MAX + 1, 1. / 7))
        # Issue 15989
        self.checkraises(OverflowError, '%*s', '__mod__',
                         (SIZE_MAX + 1, ''))
        self.checkraises(OverflowError, '%.*f', '__mod__',
                         (UINT_MAX + 1, 1. / 7))

    def test_floatformatting(self):
        # float formatting
        for prec in range(100):
            format = '%%.%if' % prec
            value = 0.01
            for x in range(60):
                value = value * 3.14159265359 / 3.0 * 10.0
                self.checkcall(format, "__mod__", value)

    def test_inplace_rewrites(self):
        # Check that strings don't copy and modify cached single-character strings
        self.checkequal('a', 'A', 'lower')
        self.checkequal(True, 'A', 'isupper')
        self.checkequal('A', 'a', 'upper')
        self.checkequal(True, 'a', 'islower')

        self.checkequal('a', 'A', 'replace', 'A', 'a')
        self.checkequal(True, 'A', 'isupper')

        self.checkequal('A', 'a', 'capitalize')
        self.checkequal(True, 'a', 'islower')

        self.checkequal('A', 'a', 'swapcase')
        self.checkequal(True, 'a', 'islower')

        self.checkequal('A', 'a', 'title')
        self.checkequal(True, 'a', 'islower')

    def test_partition(self):

        self.checkequal(('this is the par', 'ti', 'tion method'),
            'this is the partition method', 'partition', 'ti')

        # from raymond's original specification
        S = 'http://www.python.org'
        self.checkequal(('http', '://', 'www.python.org'), S, 'partition', '://')
        self.checkequal(('http://www.python.org', '', ''), S, 'partition', '?')
        self.checkequal(('', 'http://', 'www.python.org'), S, 'partition', 'http://')
        self.checkequal(('http://www.python.', 'org', ''), S, 'partition', 'org')

        self.checkraises(ValueError, S, 'partition', '')
        self.checkraises(TypeError, S, 'partition', None)

    def test_rpartition(self):

        self.checkequal(('this is the rparti', 'ti', 'on method'),
            'this is the rpartition method', 'rpartition', 'ti')

        # from raymond's original specification
        S = 'http://www.python.org'
        self.checkequal(('http', '://', 'www.python.org'), S, 'rpartition', '://')
        self.checkequal(('', '', 'http://www.python.org'), S, 'rpartition', '?')
        self.checkequal(('', 'http://', 'www.python.org'), S, 'rpartition', 'http://')
        self.checkequal(('http://www.python.', 'org', ''), S, 'rpartition', 'org')

        self.checkraises(ValueError, S, 'rpartition', '')
        self.checkraises(TypeError, S, 'rpartition', None)

    def test_none_arguments(self):
        # issue 11828
        s = 'hello'
        self.checkequal(2, s, 'find', 'l', None)
        self.checkequal(3, s, 'find', 'l', -2, None)
        self.checkequal(2, s, 'find', 'l', None, -2)
        self.checkequal(0, s, 'find', 'h', None, None)

        self.checkequal(3, s, 'rfind', 'l', None)
        self.checkequal(3, s, 'rfind', 'l', -2, None)
        self.checkequal(2, s, 'rfind', 'l', None, -2)
        self.checkequal(0, s, 'rfind', 'h', None, None)

        self.checkequal(2, s, 'index', 'l', None)
        self.checkequal(3, s, 'index', 'l', -2, None)
        self.checkequal(2, s, 'index', 'l', None, -2)
        self.checkequal(0, s, 'index', 'h', None, None)

        self.checkequal(3, s, 'rindex', 'l', None)
        self.checkequal(3, s, 'rindex', 'l', -2, None)
        self.checkequal(2, s, 'rindex', 'l', None, -2)
        self.checkequal(0, s, 'rindex', 'h', None, None)

        self.checkequal(2, s, 'count', 'l', None)
        self.checkequal(1, s, 'count', 'l', -2, None)
        self.checkequal(1, s, 'count', 'l', None, -2)
        self.checkequal(0, s, 'count', 'x', None, None)

        self.checkequal(True, s, 'endswith', 'o', None)
        self.checkequal(True, s, 'endswith', 'lo', -2, None)
        self.checkequal(True, s, 'endswith', 'l', None, -2)
        self.checkequal(False, s, 'endswith', 'x', None, None)

        self.checkequal(True, s, 'startswith', 'h', None)
        self.checkequal(True, s, 'startswith', 'l', -2, None)
        self.checkequal(True, s, 'startswith', 'h', None, -2)
        self.checkequal(False, s, 'startswith', 'x', None, None)

    def test_find_etc_raise_correct_error_messages(self):
        # issue 11828
        s = 'hello'
        x = 'x'
        self.assertRaisesRegex(TypeError, r'^find\(', s.find,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^rfind\(', s.rfind,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^index\(', s.index,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^rindex\(', s.rindex,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^count\(', s.count,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^startswith\(', s.startswith,
                                x, None, None, None)
        self.assertRaisesRegex(TypeError, r'^endswith\(', s.endswith,
                                x, None, None, None)

        # issue #15534
        self.checkequal(10, "...\u043c......<", "find", "<")


class MixinStrUnicodeTest:
    # Additional tests that only work with str.

    def test_bug1001011(self):
        # Make sure join returns a NEW object for single item sequences
        # involving a subclass.
        # Make sure that it is of the appropriate type.
        # Check the optimisation still occurs for standard objects.
        t = self.type2test
        class subclass(t):
            pass
        s1 = subclass("abcd")
        s2 = t().join([s1])
        self.assertIsNot(s1, s2)
        self.assertIs(type(s2), t)

        s1 = t("abcd")
        s2 = t().join([s1])
        self.assertIs(s1, s2)