#!/usr/bin/env python3
"""
ESP-IDF环境变量初始化函数的单元测试
"""
import unittest
import os
import sys
from pathlib import Path
from unittest.mock import patch, MagicMock
# 将项目根目录添加到Python路径中,以便导入模块
sys.path.insert(0, str(Path(__file__).parent.parent))
from core import config
class TestInitEspIdfEnv(unittest.TestCase):
"""测试init_esp_idf_env函数"""
def setUp(self):
"""测试前的准备工作"""
self.original_esp_idf_path = config.ESP_IDF_PATH
self.original_source_script = config.SOURCE_SCRIPT
# 重置全局环境变量
config.ESP_IDF_ENV = None
def tearDown(self):
"""测试后的清理工作"""
config.ESP_IDF_PATH = self.original_esp_idf_path
config.SOURCE_SCRIPT = self.original_source_script
# 重置全局环境变量
config.ESP_IDF_ENV = None
@patch('core.config.Path.exists')
@patch('subprocess.run')
def test_init_esp_idf_env_success(self, mock_run, mock_exists):
"""测试成功初始化环境变量的情况"""
# 设置mock
mock_exists.return_value = True
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "PATH=/new/path\nIDF_PATH=/esp/esp-idf\nOTHER_VAR=value\n"
mock_run.return_value = mock_result
# 调用函数
env = config.init_esp_idf_env()
# 验证结果
self.assertIsInstance(env, dict)
self.assertIn('PATH', env)
self.assertIn('IDF_PATH', env)
self.assertIn('OTHER_VAR', env)
self.assertEqual(env['IDF_PATH'], '/esp/esp-idf')
self.assertEqual(env['OTHER_VAR'], 'value')
# 验证subprocess.run被正确调用
mock_run.assert_called_once()
args, kwargs = mock_run.call_args
self.assertIn('source', args[0])
self.assertTrue(kwargs['shell'])
self.assertTrue(kwargs['capture_output'])
self.assertEqual(kwargs['text'], True)
self.assertEqual(kwargs['executable'], '/bin/bash')
@patch('core.config.Path.exists')
def test_init_esp_idf_env_script_not_found(self, mock_exists):
"""测试export.sh脚本不存在的情况"""
# 设置mock
mock_exists.return_value = False
# 验证抛出FileNotFoundError异常
with self.assertRaises(FileNotFoundError) as context:
config.init_esp_idf_env()
self.assertIn("ESP-IDF export.sh script not found", str(context.exception))
@patch('core.config.Path.exists')
@patch('subprocess.run')
def test_init_esp_idf_env_script_failure(self, mock_run, mock_exists):
"""测试执行export.sh脚本失败的情况"""
# 设置mock
mock_exists.return_value = True
mock_result = MagicMock()
mock_result.returncode = 1
mock_result.stderr = "Some error occurred"
mock_run.return_value = mock_result
# 验证抛出RuntimeError异常
with self.assertRaises(RuntimeError) as context:
config.init_esp_idf_env()
self.assertIn("Failed to source ESP-IDF environment", str(context.exception))
@patch('core.config.Path.exists')
@patch('subprocess.run')
def test_init_esp_idf_env_empty_lines_and_malformed(self, mock_run, mock_exists):
"""测试环境变量输出包含空行和格式错误行的情况"""
# 设置mock
mock_exists.return_value = True
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = "PATH=/new/path\nIDF_PATH=/esp/esp-idf\n\nMALFORMED_LINE\nOTHER_VAR=value\n=\nKEY=VALUE\n"
mock_run.return_value = mock_result
# 调用函数
env = config.init_esp_idf_env()
# 验证结果
self.assertIn('PATH', env)
self.assertIn('IDF_PATH', env)
self.assertIn('OTHER_VAR', env)
self.assertIn('KEY', env)
self.assertNotIn('', env) # 空键不应该存在
self.assertNotIn('MALFORMED_LINE', env) # 没有等号的行应该被忽略
class TestListSerialPorts(unittest.TestCase):
"""测试list_serial_ports函数"""
def setUp(self):
"""测试前的准备工作"""
# 不再需要重置server模块中的环境变量
pass
def tearDown(self):
"""测试后的清理工作"""
# 不再需要清理server模块中的环境变量
pass
@patch('serial.tools.list_ports.comports')
def test_list_serial_ports_with_valid_descriptions(self, mock_comports):
"""测试列出具有有效描述的串口设备"""
# 设置mock数据
mock_port1 = MagicMock()
mock_port1.device = '/dev/ttyUSB0'
mock_port1.description = 'USB Serial Device'
mock_port1.hwid = 'USB VID:PID=10C4:EA60 SER=0001 LOCATION=1-1.2'
mock_port2 = MagicMock()
mock_port2.device = '/dev/ttyACM0'
mock_port2.description = 'Arduino Uno'
mock_port2.hwid = 'USB VID:PID=2341:0043 SER=7523031383535101A1A2 LOCATION=1-1.3'
mock_comports.return_value = [mock_port1, mock_port2]
# 直接调用函数内部逻辑(绕过装饰器)
ports = []
for port in [mock_port1, mock_port2]:
if port.description and port.description.lower() != 'n/a':
ports.append({
'device': port.device,
'description': port.description,
'hwid': port.hwid
})
# 验证结果
self.assertEqual(len(ports), 2)
self.assertEqual(ports[0]['device'], '/dev/ttyUSB0')
self.assertEqual(ports[0]['description'], 'USB Serial Device')
self.assertEqual(ports[0]['hwid'], 'USB VID:PID=10C4:EA60 SER=0001 LOCATION=1-1.2')
self.assertEqual(ports[1]['device'], '/dev/ttyACM0')
self.assertEqual(ports[1]['description'], 'Arduino Uno')
self.assertEqual(ports[1]['hwid'], 'USB VID:PID=2341:0043 SER=7523031383535101A1A2 LOCATION=1-1.3')
@patch('serial.tools.list_ports.comports')
def test_list_serial_ports_filter_na_descriptions(self, mock_comports):
"""测试过滤掉描述为'n/a'的串口设备"""
# 设置mock数据
mock_port1 = MagicMock()
mock_port1.device = '/dev/ttyUSB0'
mock_port1.description = 'USB Serial Device'
mock_port1.hwid = 'USB VID:PID=10C4:EA60 SER=0001 LOCATION=1-1.2'
mock_port2 = MagicMock()
mock_port2.device = '/dev/ttyACM0'
mock_port2.description = 'n/a' # 应该被过滤掉
mock_port2.hwid = 'USB VID:PID=2341:0043 SER=7523031383535101A1A2 LOCATION=1-1.3'
mock_port3 = MagicMock()
mock_port3.device = '/dev/ttyAMA0'
mock_port3.description = 'N/A' # 应该被过滤掉(大小写不敏感)
mock_port3.hwid = 'UART'
# 直接调用函数内部逻辑(绕过装饰器)
ports = []
for port in [mock_port1, mock_port2, mock_port3]:
if port.description and port.description.lower() != 'n/a':
ports.append({
'device': port.device,
'description': port.description,
'hwid': port.hwid
})
# 验证结果 - 只应该返回第一个端口
self.assertEqual(len(ports), 1)
self.assertEqual(ports[0]['device'], '/dev/ttyUSB0')
self.assertEqual(ports[0]['description'], 'USB Serial Device')
if __name__ == '__main__':
unittest.main()