def setUp(self): """ 启动测试执行的初始化 """ # 初始化日志类 DebugTool.set_debug(False) try: # 删除临时日志 FileTool.remove_files(path=os.path.join( _TEST_DATA_DIR, 'temp/http_service_log/log'), regex_str='test_case*') except: pass self.logger = simple_log.Logger( conf_file_name=os.path.join( _TEST_DATA_DIR, 'tcp_ip_service/test_http_service.json'), logger_name=simple_log.EnumLoggerName.ConsoleAndFile, config_type=simple_log.EnumLoggerConfigType.JSON_FILE, logfile_path=os.path.join( _TEST_DATA_DIR, 'temp/http_service_log/log/test_case.log'), is_create_logfile_by_day=True) self.logger.setLevelWithHandler(simple_log.DEBUG) # 启动服务 self.server = HttpService( logger=self.logger, server_status_info_fun=self.server_status_info_fun, self_tag='UnitTest', log_level=simple_log.INFO, server_http_deal_fun=self.server_http_deal_fun) _server_opts = HttpService.generate_server_opts() _server_opts.ip = "127.0.0.1" _server_opts.port = 9513 self.server.start_server(server_opts=_server_opts)
def test_debugtools_1(): # 测试DebugTools - 跨模块的打印 - 增加日志类的干扰 _logger = simple_log.Logger( conf_file_name=_TEMP_DIR + '/test_debugtools_1.json', logger_name=simple_log.EnumLoggerName.ConsoleAndFile, config_type=simple_log.EnumLoggerConfigType.JSON_FILE, logfile_path=_TEMP_DIR + '/log/test_debugtools_1.log') _logger.log(simple_log.DEBUG, 'test_debugtools_1:write_log:DEBUG:1:界面应显示本日志,文件不应显示本日志') _logger.log(simple_log.INFO, 'test_debugtools_1:write_log:INFO:2:界面应显示本日志,文件应显示本日志') del _logger DebugTool.set_debug(True) DebugTool.debug_print("自己本模块的打印") test_debugtools() return
def setUpClass(cls): """ 启动测试类执行的初始化,只执行一次 """ # 初始化日志类 DebugTool.set_debug(False) try: # 删除临时日志 FileTool.remove_files(path=_TEMP_DIR + '/log/', regex_str='test_case*') except: pass cls.logger = simple_log.Logger( conf_file_name=_TEMP_DIR + '/../../simple_grpc/test_simple_grpc.json', logger_name=simple_log.EnumLoggerName.ConsoleAndFile, config_type=simple_log.EnumLoggerConfigType.JSON_FILE, logfile_path=_TEMP_DIR + '/log/test_case_asyn.log', is_create_logfile_by_day=True, ) cls.logger.setLevelWithHandler(simple_log.DEBUG) # 设置json转换对象的参数映射 # 日志处理函数 def _asyn_logging_fun(levelno, topic_name, msg): print('haha:%s, %s, %s' % (str(levelno), topic_name, msg)) # 异步日志 cls._asyn_logger = CallChainTool.create_call_chain_logger( logger=cls.logger, asyn_logging=True, asyn_logging_fun=_asyn_logging_fun, asyn_deal_msg_fun=SimpleGRpcTools.api_call_chain_asyn_deal_msg_fun) # 服务端处理类,可以多个服务公用 cls.servicer_simple_call = SimpleGRpcServicer(logger=cls._asyn_logger) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_call_para', service_simple_call_para) cls.servicer_simple_call.add_service( EnumCallMode.Simple, 'service_simple_call_no_para_no_return', service_simple_call_no_para_no_return) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_call_return', service_simple_call_return) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_throw_excepiton', service_simple_throw_excepiton) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_overtime', service_simple_overtime) cls.servicer_simple_call.add_service(EnumCallMode.ClientSideStream, 'service_client_side_stream', service_client_side_stream) cls.servicer_simple_call.add_service(EnumCallMode.ServerSideStream, 'service_server_side_stream', service_server_side_stream) cls.servicer_simple_call.add_service(EnumCallMode.BidirectionalStream, 'service_bidirectional_stream', service_bidirectional_stream) # 初始化并启动服务,简单服务,无SSL,无服务发现 cls.server_no_ssl_no_zoo_opts = SimpleGRpcServer.generate_server_opts( ip='127.0.0.1', port=50051, max_workers=10, max_connect=100, is_health_check=True) cls.server_no_ssl_no_zoo = SimpleGRpcServer( server_name='ServerNoSslNoZoo', logger=cls.logger, log_level=simple_log.INFO) cls.server_no_ssl_no_zoo.start_server( server_opts=cls.server_no_ssl_no_zoo_opts, servicer_list={'servicer_simple_call': cls.servicer_simple_call}, is_wait=True) # 初始化并启动服务,简单服务,无服务发现,TSL双向认证模式 _private_key_certificate_chain_pair = SimpleGRpcTools.get_private_key_certificate_chain_pair( _TEMP_DIR + '/../../simple_grpc/server.pem', _TEMP_DIR + '/../../simple_grpc/server.crt') with open(_TEMP_DIR + '/../../simple_grpc/client.crt', 'rb') as f: # 根证书 _root_certificates = f.read() cls.server_double_ssl_no_zoo_opts = SimpleGRpcServer.generate_server_opts( ip='localhost', port=50052, max_workers=10, max_connect=100, is_use_ssl=True, private_key_certificate_chain_pairs=( _private_key_certificate_chain_pair, ), root_certificates=_root_certificates) cls.server_double_ssl_no_zoo = SimpleGRpcServer( server_name='ServerDoubleSslNoZoo', logger=cls.logger, log_level=simple_log.INFO) cls.server_double_ssl_no_zoo.start_server( server_opts=cls.server_double_ssl_no_zoo_opts, servicer_list={'servicer_simple_call': cls.servicer_simple_call}, is_wait=True) # 初始化并启动服务,简单服务,无服务发现,TSL单向认证模式(仅验证服务端证书) cls.server_server_ssl_no_zoo_opts = SimpleGRpcServer.generate_server_opts( ip='localhost', port=50053, max_workers=10, max_connect=100, is_use_ssl=True, private_key_certificate_chain_pairs=( _private_key_certificate_chain_pair, ), root_certificates=None) cls.server_server_ssl_no_zoo = SimpleGRpcServer( server_name='ServerServerSslNoZoo', logger=cls.logger, log_level=simple_log.INFO) cls.server_server_ssl_no_zoo.start_server( server_opts=cls.server_server_ssl_no_zoo_opts, servicer_list={'servicer_simple_call': cls.servicer_simple_call}, is_wait=True)
def test_debugtools(): DebugTool.debug_print("从debug_tool_demo_not_run中的打印信息")
def __init__(self, file: str, is_resume: bool = True, file_size: int = None, md5: str = None, is_overwrite: bool = False, temp_ext: str = 'tmp', info_ext: str = 'info', extend_info: dict = None, thread_num: int = 1, block_size: int = 4096, cache_size: int = 1024, auto_expand: bool = True): """ 初始化文件保存对象 @param {str} file - 文件保存路径(含文件名) @param {bool} is_resume=True - 指定是否续传(自动查找已下载的信息), 如果不指定续传将自动删除原来已下载临时文件 注:如果指定续传,且可以找到原来的临时文件,则以下参数将使用原来的信息,如果有传入则会进行差异值的校验: file_size、md5 @param {int} file_size=None - 文件大小,单位为byte, 如果为None代表未知文件大小, 此时auto_expand参数固定为True @param {str} md5=None - 验证文件的md5字符串,如果不传代表不进行验证 @param {bool} is_overwrite=False - 是否覆盖已有文件,如果为否,则目标文件已存在的情况下抛出异常 @param {str} temp_ext='tmp' - 处理过程中临时文件扩展名 @param {str} info_ext='info' - 处理过程中信息文件扩展名 @param {dict} extend_info=None - 处理过程中要保存的信息字典,例如保存文件下载路径,引用页等信息 @param {int} thread_num=1 - 写入处理线程数量 @param {int} block_size=4096 - 每次写入块大小,单位为byte @param {int} cache_size=1024 - 单线程缓存大小,单位为kb(注意:真实缓存大小还需要乘以处理线程数量) @param {bool} auto_expand=True - 是否自动扩展文件大小(否则在初始化时会自动创建指定大小的文件) @throws {FileExistsError} - 如果下载文件已存在且不允许覆盖的情况抛出异常 @throws {FileNotFoundError} - 续传情况下临时文件不存在则抛出异常 @throws {InfoFileLockError} - 如果已打开信息文件进行文件存储处理,抛出该异常 """ # 检查文件是否存在 self._file = os.path.abspath(file) self._path, self._filename = os.path.split(self._file) if os.path.exists(self._file): # 文件已存在 if is_overwrite: FileTool.remove_file(self._file) else: raise FileExistsError('file exists: %s' % self._file) else: # 创建目录 FileTool.create_dir(self._path, exist_ok=True) # 文件信息字典,该字典登记文件基本信息和写入情况 self._info: dict = None # 锁文件,控制一个文件不能被多个类处理, 先尝试创建锁文件,如果创建失败会抛出异常 self._lock_file = os.path.join(self._path, '%s.%s' % (self._filename, 'lock')) try: self._lock_file_handle = os.open( self._lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR) except: raise InfoFileLockError('info file is locked') try: # 获取是否debug状态 self._debug_on = DebugTool.is_debug_on() self._lock_print_timeout = None if self._debug_on: self._lock_print_timeout = 5.0 # 打印锁等待超时时间 # 处理信息字典、临时文件、信息文件 self._temp_file = os.path.join( self._path, '%s.%s' % (self._filename, temp_ext)) self._info_file = os.path.join( self._path, '%s.%s' % (self._filename, info_ext)) self._auto_expand = auto_expand self._thread_num = thread_num self._block_size = block_size self._cache_size = cache_size * 1024 # 数据处理锁 self._cache_info_lock = threading.RLock() # 缓存信息更新锁 self._tmp_file_lock = threading.RLock() # 缓存文件写入锁 self._is_finished = False # 要控制的完成状态 self._dealed_finished_lock = threading.RLock() # 控制多线程操作结束函数的状态更新锁 self._dealed_finished = False # 控制多线程操作结束函数只执行一次的变量 if is_resume and os.path.exists(self._info_file): # 自动续传情况 self._info_file_handle = open(self._info_file, 'r+', encoding='utf-8') self._info_file_handle.seek(0) self._info = json.loads(self._info_file_handle.read()) # 检查传入信息是否一致 if file_size is not None and file_size != self._info[ 'file_size']: raise AttributeError( 'resume info [file_size] inconsistency, info file [%s], now [%s]' % (str(self._info['file_size']), str(file_size))) if md5 is not None and md5 != self._info['md5']: raise AttributeError( 'resume info [md5] inconsistency, info file [%s], now [%s]' % (self._info['md5'], md5)) # 检查临时文件 self._temp_file = os.path.join(self._path, self._info['tmp_file']) if not os.path.exists(self._temp_file): # 临时文件不存在 raise FileNotFoundError('temp file is not found: %s' % self._temp_file) self._tmp_file_handle = open(self._temp_file, 'rb+') self._tmp_file_handle.seek(0) else: # 删除已存在的临时文件信息 if os.path.exists(self._temp_file): FileTool.remove_file(self._temp_file) if os.path.exists(self._info_file): FileTool.remove_file(self._info_file) # 形成信息字典 self._info = { 'tmp_file': '%s.%s' % (self._filename, temp_ext), # 临时文件名称 'file_size': -1 if file_size is None else file_size, # 文件大小 'write_size': 0, # 已写入数据大小 'md5': '' if md5 is None else md5, # md5校验值 'extend_info': {} if extend_info is None else extend_info, # 传入的扩展信息 # 存储索引,按位置顺序在数组中登记未写入区间,数组每一项登记未写入数据的开始位置和结束位置 'store_index': [[0, file_size - 1]] } # 生成临时文件 self._tmp_file_handle = open(self._temp_file, 'wb') if not auto_expand and file_size is not None: # 直接生成指定大小的文件 self._tmp_file_handle.seek(file_size - 1) # 跳到指定位置 self._tmp_file_handle.write(b'\x00') # 一定要写入一个字符,否则无效 self._tmp_file_handle.flush() # 写入信息字典文件 self._info_file_handle = open(self._info_file, 'w', encoding='utf-8') self._write_info_file() # 合并存储索引,把碎片合并成为大块 self._info['store_index'] = self._f_merge_store_index( self._info['store_index']) # 初始化缓存等信息 if self._info['file_size'] == -1: # 如果没有文件大小的情况,不支持拆分多写入线程和一次性创建指定大小文件的情况 self._thread_num = 1 self._auto_expand = True # 缓存处理 self._max_cache_pos = [ -1, ] # 当前缓存分配到的区域最大位置 self._cache = dict() for _i in range(self._thread_num): self._cache[_i] = { 'start': -1, # 缓存数据对应文件的写入位置, -1代表没有设置 'size': 0, # 缓存数据大小 'buffer': bytes(), # 具体的缓存数据 'end_pos': -1, # 该缓存对应线程要处理的文件块结束位置 'lock': threading.RLock(), # 用于缓存线程处理的锁) 'get_start': -1, # 当前正在获取的数据的开始位置 'get_size': 0, # 当前要获取数据的大小 } # 分配每个缓存要处理文件区域 for _i in range(self._thread_num): self._set_cache_area(_i) except: # 如果初始化出现异常,清理文件句柄及锁文件 self._clear_file_handle_and_lock() raise
def setUpClass(cls): """ 启动测试类执行的初始化,只执行一次 """ # 初始化日志类 DebugTool.set_debug(False) try: # 删除临时日志 FileTool.remove_files(path=_TEMP_DIR + '/log/', regex_str='test_case_client*') except: pass cls.logger = simple_log.Logger( conf_file_name=_TEMP_DIR + '/../../simple_grpc/test_simple_grpc.json', logger_name=simple_log.EnumLoggerName.ConsoleAndFile, config_type=simple_log.EnumLoggerConfigType.JSON_FILE, logfile_path=_TEMP_DIR + '/log/test_case_client.log', is_create_logfile_by_day=True, ) cls.logger.setLevelWithHandler(simple_log.DEBUG) # 设置json转换对象的参数映射 # 日志处理函数 def _asyn_logging_fun(levelno, topic_name, msg): print('haha client:%s, %s, %s' % (str(levelno), topic_name, msg)) # 异步日志 cls._asyn_logger = CallChainTool.create_call_chain_logger( logger=cls.logger, asyn_logging=False, asyn_logging_fun=_asyn_logging_fun, asyn_deal_msg_fun=SimpleGRpcTools.api_call_chain_asyn_deal_msg_fun) RunTool.set_global_logger(cls._asyn_logger) # 服务端处理类,可以多个服务公用 cls.servicer_simple_call = SimpleGRpcServicer( logger=None, is_use_global_logger=False) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_call_para', service_simple_call_para) cls.servicer_simple_call.add_service( EnumCallMode.Simple, 'service_simple_call_no_para_no_return', service_simple_call_no_para_no_return) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_call_return', service_simple_call_return) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_throw_excepiton', service_simple_throw_excepiton) cls.servicer_simple_call.add_service(EnumCallMode.Simple, 'service_simple_overtime', service_simple_overtime) # 初始化并启动服务,简单服务,无SSL,无服务发现 cls.server_no_ssl_no_zoo_opts = SimpleGRpcServer.generate_server_opts( ip='127.0.0.1', port=50051, max_workers=10, max_connect=100, is_health_check=True) cls.server_no_ssl_no_zoo = SimpleGRpcServer( server_name='ServerNoSslNoZoo', logger=cls._asyn_logger, log_level=simple_log.INFO) cls.server_no_ssl_no_zoo.start_server( server_opts=cls.server_no_ssl_no_zoo_opts, servicer_list={'servicer_simple_call': cls.servicer_simple_call}, is_wait=True)