Files
android-assistant/tests/core/test_adb_manager.py
T

336 lines
14 KiB
Python

"""Tests for core ADB manager functionality."""
import pytest
import unittest.mock as mock
import os
import tempfile
import subprocess
import sys
from unittest.mock import MagicMock, patch, mock_open
from src.core.adb_manager import (
ADBManager,
get_executable_directory,
get_platform_tools_directory,
get_adb_binary_path,
is_adb_available,
get_platform_type,
ensure_platform_tools_in_user_dir
)
class TestADBManagerUtilityFunctions:
"""Test utility functions in adb_manager module."""
def test_get_executable_directory_frozen(self):
"""Test get_executable_directory when running as frozen executable."""
with patch.object(sys, 'frozen', True, create=True):
with patch('sys.executable', '/path/to/executable'):
result = get_executable_directory()
assert result == '/path/to'
def test_get_executable_directory_script(self):
"""Test get_executable_directory when running as script."""
with patch.object(sys, 'frozen', False, create=True):
with patch('os.path.abspath') as mock_abspath:
with patch('os.path.dirname') as mock_dirname:
mock_abspath.return_value = '/path/to/script.py'
mock_dirname.return_value = '/path/to'
result = get_executable_directory()
assert result == '/path/to'
def test_get_platform_type(self):
"""Test platform type detection."""
# Test current platform (should be linux based on test output)
result = get_platform_type()
assert result in ['linux', 'windows', 'darwin']
# Test mocked platforms by patching the OS_TYPE variable
with patch('src.core.adb_manager.OS_TYPE', 'win32'):
result = get_platform_type()
assert result == 'win32'
with patch('src.core.adb_manager.OS_TYPE', 'darwin'):
result = get_platform_type()
assert result == 'darwin'
@patch('src.core.adb_manager.ensure_platform_tools_in_user_dir')
@patch('os.path.isfile')
def test_is_adb_available_system_path(self, mock_isfile, mock_ensure):
"""Test ADB availability check when ADB binary is found."""
# Mock ensure_platform_tools_in_user_dir to return a path
mock_ensure.return_value = '/path/to/adb'
mock_isfile.return_value = True # File exists
result = is_adb_available()
assert result is True
mock_ensure.assert_called_once()
# isfile might be called multiple times (once inside ensure_platform_tools_in_user_dir)
assert mock_isfile.called
mock_isfile.assert_any_call('/path/to/adb')
@patch('shutil.which')
@patch('os.path.isfile')
def test_is_adb_available_platform_tools(self, mock_isfile, mock_which):
"""Test ADB availability check from platform-tools directory."""
mock_which.return_value = None # Not in PATH
mock_isfile.return_value = True # But exists in platform-tools
assert is_adb_available() is True
@patch('shutil.which')
@patch('os.path.isfile')
def test_is_adb_available_not_found(self, mock_isfile, mock_which):
"""Test ADB availability check when ADB is not found."""
mock_which.return_value = None
mock_isfile.return_value = False
assert is_adb_available() is False
class TestADBManager:
"""Test ADBManager class functionality."""
@pytest.fixture
def adb_manager(self):
"""Create an ADBManager instance for testing."""
with patch('src.core.adb_manager.is_adb_available', return_value=True):
return ADBManager()
def test_init(self, adb_manager):
"""Test ADBManager initialization."""
assert adb_manager.progress_callback is None
assert adb_manager.status_callback is None
assert adb_manager.current_process is None
assert adb_manager.deduplicator is not None
assert 'current_file' in adb_manager.transfer_progress
assert 'total_files' in adb_manager.transfer_progress
def test_set_progress_callback(self, adb_manager):
"""Test setting progress callback."""
callback = MagicMock()
adb_manager.set_progress_callback(callback)
assert adb_manager.progress_callback == callback
def test_set_status_callback(self, adb_manager):
"""Test setting status callback."""
callback = MagicMock()
adb_manager.set_status_callback(callback)
assert adb_manager.status_callback == callback
def test_update_progress(self, adb_manager):
"""Test progress update with callback."""
callback = MagicMock()
adb_manager.set_progress_callback(callback)
adb_manager._update_progress(50)
callback.assert_called_once_with(50)
def test_update_progress_no_callback(self, adb_manager):
"""Test progress update without callback."""
# Should not raise an exception
adb_manager._update_progress(50)
def test_update_status(self, adb_manager):
"""Test status update with callback."""
callback = MagicMock()
adb_manager.set_status_callback(callback)
adb_manager._update_status("Test status")
callback.assert_called_once_with("Test status")
def test_update_status_no_callback(self, adb_manager):
"""Test status update without callback."""
# Should not raise an exception
adb_manager._update_status("Test status")
def test_update_transfer_progress(self, adb_manager):
"""Test transfer progress calculation."""
status_callback = MagicMock()
adb_manager.set_status_callback(status_callback)
adb_manager._update_transfer_progress(3, 10)
# Should update internal progress tracking
assert adb_manager.transfer_progress['current_file'] == 3
assert adb_manager.transfer_progress['total_files'] == 10
# Should call status callback with special format
status_callback.assert_called_once_with("TRANSFER_PROGRESS:3:10")
def test_reset_transfer_progress(self, adb_manager):
"""Test transfer progress reset."""
# Set some progress values
adb_manager.transfer_progress['current_file'] = 5
adb_manager.transfer_progress['total_files'] = 10
adb_manager.transfer_progress['files_to_transfer'] = 10
adb_manager._reset_transfer_progress()
assert adb_manager.transfer_progress['current_file'] == 0
assert adb_manager.transfer_progress['total_files'] == 0
assert adb_manager.transfer_progress['files_to_transfer'] == 0
@patch('shutil.disk_usage')
@patch('src.core.adb_manager.get_platform_tools_directory')
def test_check_local_disk_space_sufficient(self, mock_get_dir, mock_disk_usage, adb_manager):
"""Test disk space check with sufficient space."""
mock_get_dir.return_value = '/fake/path'
mock_disk_usage.return_value = (1000*1024*1024, 500*1024*1024, 200*1024*1024) # total, used, free (200MB)
assert adb_manager.check_local_disk_space() is True
@patch('shutil.disk_usage')
@patch('src.core.adb_manager.get_platform_tools_directory')
def test_check_local_disk_space_insufficient(self, mock_get_dir, mock_disk_usage, adb_manager):
"""Test disk space check with insufficient space."""
mock_get_dir.return_value = '/fake/path'
mock_disk_usage.return_value = (1000*1024*1024, 980*1024*1024, 20*1024*1024) # total, used, free (20MB < 50MB)
with pytest.raises(Exception, match="Insufficient disk space"):
adb_manager.check_local_disk_space()
@patch('src.core.adb_manager.get_adb_binary_path')
@patch('subprocess.run')
def test_run_adb_command_success(self, mock_run, mock_get_path, adb_manager):
"""Test successful ADB command execution."""
mock_get_path.return_value = '/fake/adb'
mock_result = MagicMock()
mock_result.stdout = "success output"
mock_result.stderr = ""
mock_result.returncode = 0
mock_run.return_value = mock_result
stdout, stderr, returncode = adb_manager.run_adb_command(['devices'])
assert returncode == 0
assert stdout == "success output"
assert stderr == ""
@patch('src.core.adb_manager.get_adb_binary_path')
@patch('subprocess.run')
def test_run_adb_command_failure(self, mock_run, mock_get_path, adb_manager):
"""Test failed ADB command execution."""
mock_get_path.return_value = '/fake/adb'
mock_run.side_effect = Exception("Command failed")
stdout, stderr, returncode = adb_manager.run_adb_command(['devices'])
assert stdout is None
assert "Command failed" in stderr
assert returncode == -1
@patch('src.core.adb_manager.ADBManager.run_adb_command')
def test_check_device_connected(self, mock_run_command, adb_manager):
"""Test device detection when device is connected."""
mock_run_command.return_value = ("ABC123\tdevice\n", "", 0)
device_id = adb_manager.check_device()
assert device_id == "ABC123"
mock_run_command.assert_called_once_with(['devices'], capture_output=True)
@patch('src.core.adb_manager.ADBManager.run_adb_command')
def test_check_device_not_connected(self, mock_run_command, adb_manager):
"""Test device detection when no device is connected."""
mock_run_command.return_value = ("List of devices attached\n\n", "", 0)
device_id = adb_manager.check_device()
assert device_id is None
def test_parse_progress_valid(self, adb_manager):
"""Test progress parsing with valid input."""
test_line = "/sdcard/test.txt: (100%)"
result = adb_manager.parse_progress(test_line)
assert result == 100
test_line = "/sdcard/folder/file.jpg: (45%)"
result = adb_manager.parse_progress(test_line)
assert result == 45
# Test other patterns
test_line = "75% complete"
result = adb_manager.parse_progress(test_line)
assert result == 75
test_line = "transferred 50%"
result = adb_manager.parse_progress(test_line)
assert result == 50
def test_parse_progress_invalid(self, adb_manager):
"""Test progress parsing with invalid input."""
test_line = "Some random text"
result = adb_manager.parse_progress(test_line)
assert result is None
test_line = "[ 50%] invalid format"
result = adb_manager.parse_progress(test_line)
assert result is None
def test_cancel_transfer(self, adb_manager):
"""Test transfer cancellation."""
# Set up a mock process
mock_process = MagicMock()
adb_manager.current_process = mock_process
result = adb_manager.cancel_transfer()
assert result is True
mock_process.terminate.assert_called_once()
mock_process.wait.assert_called()
assert adb_manager.current_process is None
class TestEnsurePlatformToolsInUserDir:
"""Test the ensure_platform_tools_in_user_dir function."""
@patch('src.core.adb_manager.get_platform_type')
@patch('requests.get')
@patch('tempfile.mkdtemp')
@patch('os.path.exists')
@patch('os.makedirs')
@patch('shutil.move')
@patch('os.symlink')
@patch('os.path.islink')
@patch('os.unlink')
def test_ensure_platform_tools_download_success(
self, mock_unlink, mock_islink, mock_symlink, mock_move,
mock_makedirs, mock_exists, mock_mkdtemp, mock_get, mock_platform_type
):
"""Test successful platform tools download and installation."""
# Setup mocks
mock_platform_type.return_value = 'linux'
mock_exists.return_value = False
mock_mkdtemp.return_value = '/tmp/test'
mock_islink.return_value = False
# Mock HTTP response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.content = b'fake zip content'
mock_get.return_value = mock_response
# Mock zipfile extraction
with patch('zipfile.ZipFile') as mock_zip:
mock_zip_instance = MagicMock()
mock_zip.return_value.__enter__.return_value = mock_zip_instance
# Mock file operations
with patch('builtins.open', mock_open()) as mock_file:
with patch('os.listdir') as mock_listdir:
mock_listdir.return_value = ['platform-tools'] # Simulate extracted folder
with patch('os.path.isdir') as mock_isdir:
# Return True for the candidate directory check
mock_isdir.return_value = True
with patch('os.path.expanduser') as mock_expanduser:
mock_expanduser.return_value = '/home/user/.local/share/android-file-handler'
result = ensure_platform_tools_in_user_dir()
# Verify the result is a path to adb
assert 'adb' in result
# Verify download was attempted
mock_get.assert_called_once()
# Verify extraction was attempted
mock_zip_instance.extractall.assert_called_once()
if __name__ == '__main__':
pytest.main([__file__])