Skip to content

Commit 203e28d

Browse files
committed
Add Install Tests w/ Logging Traces
1 parent 4e27c49 commit 203e28d

File tree

4 files changed

+91
-128
lines changed

4 files changed

+91
-128
lines changed

cppython/plugins/conan/plugin.py

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
installation, and synchronization with other tools.
66
"""
77

8+
import logging
9+
import subprocess
810
from pathlib import Path
911
from typing import Any
1012

@@ -70,51 +72,73 @@ def information() -> Information:
7072
return Information()
7173

7274
def _install_dependencies(self, *, update: bool = False) -> None:
73-
"""Common implementation for installing/updating dependencies.
75+
"""Install/update dependencies using conan CLI command.
7476
7577
Args:
7678
update: If True, check remotes for newer versions/revisions and install those.
7779
If False, use cached versions when available.
7880
"""
7981
try:
82+
logger = logging.getLogger('cppython.conan')
83+
logger.debug('Starting dependency installation/update (update=%s)', update)
84+
8085
resolved_dependencies = [resolve_conan_dependency(req) for req in self.core_data.cppython_data.dependencies]
86+
logger.debug(
87+
'Resolved %d dependencies: %s', len(resolved_dependencies), [str(dep) for dep in resolved_dependencies]
88+
)
8189

90+
# Generate conanfile.py
8291
self.builder.generate_conanfile(self.core_data.project_data.project_root, resolved_dependencies)
92+
logger.debug('Generated conanfile.py at %s', self.core_data.project_data.project_root)
8393

94+
# Ensure build directory exists
8495
self.core_data.cppython_data.build_path.mkdir(parents=True, exist_ok=True)
96+
logger.debug('Created build path: %s', self.core_data.cppython_data.build_path)
8597

86-
# Install/update dependencies using Conan API
98+
# Build conan install command
8799
project_root = self.core_data.project_data.project_root
88100
conanfile_path = project_root / 'conanfile.py'
89101

90-
if conanfile_path.exists():
91-
# Initialize Conan API
92-
conan_api = ConanAPI()
93-
94-
# Get default profiles
95-
profile_host_path = conan_api.profiles.get_default_host()
96-
profile_build_path = conan_api.profiles.get_default_build()
97-
profile_host = conan_api.profiles.get_profile([profile_host_path])
98-
profile_build = conan_api.profiles.get_profile([profile_build_path])
99-
100-
# Build dependency graph for the package
101-
deps_graph = conan_api.graph.load_graph_consumer(
102-
path=str(conanfile_path),
103-
name=None,
104-
version=None,
105-
user=None,
106-
channel=None,
107-
profile_host=profile_host,
108-
profile_build=profile_build,
109-
lockfile=None,
110-
remotes=conan_api.remotes.list(),
111-
update=update,
112-
check_updates=update,
113-
is_build_require=False,
114-
)
102+
if not conanfile_path.exists():
103+
raise ProviderInstallationError('conan', 'Generated conanfile.py not found')
104+
105+
# Prepare conan install command
106+
cmd = [
107+
'conan',
108+
'install',
109+
str(conanfile_path),
110+
'--output-folder',
111+
str(self.core_data.cppython_data.build_path),
112+
'--build',
113+
'missing',
114+
]
115+
116+
if update:
117+
cmd.extend(['--update'])
118+
119+
logger.debug('Running conan command: %s', ' '.join(cmd))
115120

116-
# Install dependencies
117-
conan_api.install.install_binaries(deps_graph=deps_graph, remotes=conan_api.remotes.list())
121+
# Execute conan install command
122+
result = subprocess.run(cmd, cwd=str(project_root), capture_output=True, text=True, check=False)
123+
124+
# Log output for debugging
125+
if result.stdout:
126+
logger.debug('Conan stdout:\n%s', result.stdout)
127+
if result.stderr:
128+
logger.debug('Conan stderr:\n%s', result.stderr)
129+
130+
# Check for success
131+
if result.returncode != 0:
132+
error_msg = f'Conan install failed with return code {result.returncode}'
133+
if result.stderr:
134+
error_msg += f': {result.stderr}'
135+
raise ProviderInstallationError('conan', error_msg)
136+
137+
logger.debug('Successfully installed dependencies using conan CLI')
138+
139+
except subprocess.SubprocessError as e:
140+
operation = 'update' if update else 'install'
141+
raise ProviderInstallationError('conan', f'Failed to {operation} dependencies: {e}', e) from e
118142
except Exception as e:
119143
operation = 'update' if update else 'install'
120144
error_msg = str(e)

examples/conan_cmake/simple/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ provider-name = "conan"
1818

1919
install-path = "install"
2020

21-
dependencies = ["fmt>=11.0.1"]
21+
dependencies = ["fmt>=11.2.0"]
2222

2323
[tool.cppython.generator]
2424

tests/integration/examples/test_conan_cmake.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def test_simple(example_runner: CliRunner) -> None:
2525
"""Simple project"""
2626
# Create project configuration
2727
project_root = Path.cwd()
28-
project_configuration = ProjectConfiguration(project_root=project_root, version=None)
28+
project_configuration = ProjectConfiguration(project_root=project_root, version=None, verbosity=2, debug=True)
2929

3030
# Create console interface
3131
interface = ConsoleInterface()

tests/unit/plugins/conan/test_install.py

Lines changed: 36 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,11 @@
66

77
import pytest
88
from packaging.requirements import Requirement
9+
from pytest_mock import MockerFixture
910

1011
from cppython.plugins.conan.plugin import ConanProvider
1112
from cppython.test.pytest.mixins import ProviderPluginTestMixin
13+
from cppython.utility.exception import ProviderInstallationError
1214

1315
# Use shared fixtures
1416
pytest_plugins = ['tests.fixtures.conan']
@@ -44,21 +46,27 @@ def fixture_plugin_type() -> type[ConanProvider]:
4446

4547
def test_install_with_dependencies(
4648
self,
49+
mocker: MockerFixture,
4750
plugin: ConanProvider,
48-
conan_mock_api: Mock,
4951
conan_temp_conanfile: Path,
5052
conan_mock_dependencies: list[Requirement],
5153
conan_setup_mocks: dict[str, Mock],
5254
) -> None:
5355
"""Test install method with dependencies and existing conanfile
5456
5557
Args:
58+
mocker: Pytest mocker fixture
5659
plugin: The plugin instance
57-
conan_mock_api: Mock ConanAPI instance
5860
conan_temp_conanfile: Path to temporary conanfile.py
5961
conan_mock_dependencies: List of mock dependencies
6062
conan_setup_mocks: Dictionary containing all mocks
6163
"""
64+
# Setup subprocess mock to return success
65+
mock_subprocess = mocker.patch('cppython.plugins.conan.plugin.subprocess.run')
66+
mock_subprocess.return_value.returncode = 0
67+
mock_subprocess.return_value.stdout = 'Install completed successfully'
68+
mock_subprocess.return_value.stderr = ''
69+
6270
# Setup dependencies
6371
plugin.core_data.cppython_data.dependencies = conan_mock_dependencies
6472

@@ -79,124 +87,55 @@ def test_install_with_dependencies(
7987
# Verify build path was created
8088
assert plugin.core_data.cppython_data.build_path.exists()
8189

82-
# Verify Conan API calls
83-
conan_mock_api.profiles.get_default_host.assert_called_once()
84-
conan_mock_api.profiles.get_default_build.assert_called_once()
85-
conan_mock_api.profiles.get_profile.assert_called()
86-
conan_mock_api.graph.load_graph_consumer.assert_called_once()
87-
conan_mock_api.install.install_binaries.assert_called_once()
88-
89-
# Verify graph consumer call arguments
90-
load_graph_args = conan_mock_api.graph.load_graph_consumer.call_args
91-
assert load_graph_args[1]['path'] == str(conan_temp_conanfile)
92-
assert load_graph_args[1]['update'] is False
93-
assert load_graph_args[1]['check_updates'] is False
94-
95-
def test_install_without_conanfile(
96-
self,
97-
plugin: ConanProvider,
98-
conan_mock_api: Mock,
99-
conan_mock_dependencies: list[Requirement],
100-
conan_setup_mocks: dict[str, Mock],
101-
) -> None:
102-
"""Test install method when conanfile.py doesn't exist
103-
104-
Args:
105-
plugin: The plugin instance
106-
conan_mock_api: Mock ConanAPI instance
107-
conan_mock_dependencies: List of mock dependencies
108-
conan_setup_mocks: Dictionary containing all mocks
109-
"""
110-
# Setup dependencies
111-
plugin.core_data.cppython_data.dependencies = [conan_mock_dependencies[0]]
112-
113-
# Execute
114-
plugin.install()
115-
116-
# Verify builder was called
117-
conan_setup_mocks['builder'].generate_conanfile.assert_called_once()
118-
119-
# Verify build path was created
120-
assert plugin.core_data.cppython_data.build_path.exists()
121-
122-
# Verify Conan API calls were NOT made (no conanfile.py)
123-
conan_mock_api.profiles.get_default_host.assert_not_called()
124-
conan_mock_api.profiles.get_default_build.assert_not_called()
125-
conan_mock_api.profiles.get_profile.assert_not_called()
126-
conan_mock_api.graph.load_graph_consumer.assert_not_called()
127-
conan_mock_api.install.install_binaries.assert_not_called()
128-
129-
def test_install_no_dependencies(
130-
self,
131-
plugin: ConanProvider,
132-
conan_mock_api: Mock,
133-
conan_temp_conanfile: Path,
134-
conan_setup_mocks: dict[str, Mock],
135-
) -> None:
136-
"""Test install method with no dependencies
90+
# Verify subprocess was called with correct command
91+
mock_subprocess.assert_called_once()
92+
call_args = mock_subprocess.call_args
93+
cmd = call_args[0][0]
13794

138-
Args:
139-
plugin: The plugin instance
140-
conan_mock_api: Mock ConanAPI instance
141-
conan_temp_conanfile: Path to temporary conanfile.py
142-
conan_setup_mocks: Dictionary containing all mocks
143-
"""
144-
# No dependencies
145-
plugin.core_data.cppython_data.dependencies = []
146-
147-
# Execute
148-
plugin.install()
149-
150-
# Verify builder was called with empty dependencies
151-
conan_setup_mocks['builder'].generate_conanfile.assert_called_once()
152-
assert len(conan_setup_mocks['builder'].generate_conanfile.call_args[0][1]) == 0
153-
154-
# Verify dependency resolution was not called
155-
conan_setup_mocks['resolve_conan_dependency'].assert_not_called()
156-
157-
# Verify build path was created
158-
assert plugin.core_data.cppython_data.build_path.exists()
95+
# Check command structure
96+
assert cmd[0] == 'conan'
97+
assert cmd[1] == 'install'
98+
assert cmd[2] == str(conan_temp_conanfile)
99+
assert '--output-folder' in cmd
100+
assert '--build' in cmd
101+
assert 'missing' in cmd
102+
assert '--update' not in cmd # install mode, not update
159103

160-
# Verify Conan API calls were still made (conanfile.py exists)
161-
conan_mock_api.profiles.get_default_host.assert_called_once()
162-
conan_mock_api.profiles.get_default_build.assert_called_once()
163-
conan_mock_api.profiles.get_profile.assert_called()
164-
conan_mock_api.graph.load_graph_consumer.assert_called_once()
165-
conan_mock_api.install.install_binaries.assert_called_once()
104+
# Check working directory
105+
assert call_args[1]['cwd'] == str(plugin.core_data.project_data.project_root)
166106

167-
def test_install_conan_api_failure(
107+
def test_install_conan_command_failure(
168108
self,
109+
mocker: MockerFixture,
169110
plugin: ConanProvider,
170-
conan_mock_api: Mock,
171111
conan_temp_conanfile: Path,
172112
conan_mock_dependencies: list[Requirement],
173113
conan_setup_mocks: dict[str, Mock],
174114
) -> None:
175-
"""Test install method when Conan API calls fail
115+
"""Test install method when conan command fails
176116
177117
Args:
118+
mocker: Pytest mocker fixture
178119
plugin: The plugin instance
179-
conan_mock_api: Mock ConanAPI instance
180120
conan_temp_conanfile: Path to temporary conanfile.py
181121
conan_mock_dependencies: List of mock dependencies
182122
conan_setup_mocks: Dictionary containing all mocks
183123
"""
184-
# Make API call fail
185-
conan_mock_api.graph.load_graph_consumer.side_effect = Exception('Conan graph load failed')
124+
# Make subprocess return failure
125+
mock_subprocess = mocker.patch('cppython.plugins.conan.plugin.subprocess.run')
126+
mock_subprocess.return_value.returncode = 1
127+
mock_subprocess.return_value.stdout = ''
128+
mock_subprocess.return_value.stderr = 'Conan install failed: package not found'
186129

187130
# Add a dependency
188131
plugin.core_data.cppython_data.dependencies = [conan_mock_dependencies[0]]
189132

190133
# Execute and verify exception is raised
191-
with pytest.raises(Exception, match='Conan graph load failed'):
134+
with pytest.raises(ProviderInstallationError, match='Conan install failed with return code 1'):
192135
plugin.install()
193136

194137
# Verify builder was still called
195138
conan_setup_mocks['builder'].generate_conanfile.assert_called_once()
196139

197-
# Verify API was called up to the point of failure
198-
conan_mock_api.profiles.get_default_host.assert_called_once()
199-
conan_mock_api.profiles.get_default_build.assert_called_once()
200-
conan_mock_api.profiles.get_profile.assert_called()
201-
conan_mock_api.graph.load_graph_consumer.assert_called_once()
202-
conan_mock_api.install.install_binaries.assert_not_called()
140+
# Verify subprocess was called
141+
mock_subprocess.assert_called_once()

0 commit comments

Comments
 (0)