Skip to content

Commit 64360c6

Browse files
committed
Fix SonarCloud violations
Python fixes: - Fix _argparse.py: Change return type of _load_from_toml from None to argparse.Namespace to match actual return value - Fix _cmake.py: Reduce cognitive complexity by extracting helper methods (_process_keyword_cmake_args, _process_flag_cmake_args, _process_platform_cmake_args) - Fix _cmake_test.py: Reduce cognitive complexity by extracting test helpers and rename InterProcessReaderWriterLock variables to follow naming conventions GitHub Actions security fixes: - Use environment variables instead of direct interpolation for user-controlled data in run commands (pull_request.yml, format.yml, publish_release.yml) - Add input validation for workflow_dispatch tag parameter (draft_release.yml) Shell script fixes (run_tests.sh): - Replace [ ] with [[ ]] for safer conditional tests - Add explicit return statements at end of functions - Merge nested if conditions where applicable
1 parent 31f4f29 commit 64360c6

8 files changed

Lines changed: 145 additions & 125 deletions

File tree

.github/workflows/draft_release.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,16 @@ jobs:
1313
new-release:
1414
name: "Draft a new release"
1515
runs-on: ubuntu-latest
16+
env:
17+
RELEASE_TAG: ${{ github.event.inputs.tag }}
1618
steps:
19+
- name: Validate release tag format
20+
run: |
21+
if ! echo "$RELEASE_TAG" | grep -qE '^v[0-9]+\.[0-9]+\.[0-9]+$'; then
22+
echo "Error: Tag must be in format vXX.YY.ZZ"
23+
exit 1
24+
fi
25+
1726
- uses: actions/checkout@v4
1827

1928
- name: Setup Python
@@ -26,7 +35,7 @@ jobs:
2635
- name: Update CHANGELOG
2736
run: |
2837
python3 -m pip install mdformat-gfm 'git+https://github.com/Takishima/keepachangelog@v1.0.1'
29-
python3 -m keepachangelog release "${{ github.event.inputs.tag }}"
38+
python3 -m keepachangelog release "$RELEASE_TAG"
3039
python3 -m mdformat CHANGELOG.md
3140
3241
- name: Commit changes

.github/workflows/format.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,18 @@ jobs:
2828
curl -LsSf "${source_url}/${parse_changelog_tag}/parse-changelog-${target}.tar.gz" | tar xzf -
2929
- name: Extract version from branch name (for release branches)
3030
if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/')
31+
env:
32+
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
3133
run: |
32-
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
3334
VERSION=${BRANCH_NAME#release/v}
3435
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
3536
git tag v${RELEASE_VERSION}
3637
3738
- name: Extract version from branch name (for hotfix branches)
3839
if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/')
40+
env:
41+
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
3942
run: |
40-
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
4143
VERSION=${BRANCH_NAME#hotfix/v}
4244
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
4345
git tag v${RELEASE_VERSION}

.github/workflows/publish_release.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,18 @@ jobs:
3939
4040
- name: Extract version from branch name (for release branches)
4141
if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'release/')
42+
env:
43+
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
4244
run: |
43-
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
4445
VERSION=${BRANCH_NAME#release/}
4546
VERSION=${VERSION#v}
4647
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV
4748
4849
- name: Extract version from branch name (for hotfix branches)
4950
if: github.event_name == 'pull_request' && startsWith(github.event.pull_request.head.ref, 'hotfix/')
51+
env:
52+
BRANCH_NAME: ${{ github.event.pull_request.head.ref }}
5053
run: |
51-
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
5254
VERSION=${BRANCH_NAME#hotfix/}
5355
VERSION=${VERSION#v}
5456
echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV

.github/workflows/pull_request.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@ jobs:
3131
python3 -m pip install 'git+https://github.com/Takishima/pre-commit-changelog-auto-update@v1.0.0'
3232
3333
- name: Run Python script
34+
env:
35+
PR_BODY: ${{ github.event.pull_request.body }}
3436
run: |
35-
python3 -m update_changelog --pr-body "${{ github.event.pull_request.body }}"
37+
python3 -m update_changelog --pr-body "$PR_BODY"
3638
3739
- name: Commit changes
3840
id: commit

cmake_pc_hooks/_argparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ def _load_from_toml( # noqa: PLR0913
293293
path_must_exist: bool = True,
294294
section_must_exist: bool = True,
295295
overridable_keys: set | None = None,
296-
) -> None:
296+
) -> argparse.Namespace:
297297
"""
298298
Load a TOML file and set the attributes within the argparse namespace object.
299299

cmake_pc_hooks/_cmake.py

Lines changed: 49 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -298,40 +298,8 @@ def resolve_build_directory(self, build_dir_list=None, *, automatic_discovery=Tr
298298
self.build_dir,
299299
)
300300

301-
def setup_cmake_args(self, cmake_args): # noqa: C901
302-
"""
303-
Setup CMake arguments.
304-
305-
Args:
306-
cmake_args: Dictionary-like data structure with following keys:
307-
- 'defines': list[str]
308-
- 'undefined': list[str]
309-
- 'errors': list[str]
310-
- 'no_errors': list[str]
311-
- 'generator': str
312-
- 'toolset': str
313-
- 'platform': str
314-
- 'preset': str
315-
- 'cmake': str
316-
- 'dev_warnings': bool
317-
- 'no_dev_warnings': bool
318-
- 'linux': list[str]
319-
- 'mac': list[str]
320-
- 'win': list[str]
321-
"""
322-
self.source_dir = Path(cmake_args.source_dir).resolve()
323-
if cmake_args.cmake:
324-
self.command = [Path(cmake_args.cmake).resolve()]
325-
self.no_cmake_configure = cmake_args.no_cmake_configure
326-
327-
self.resolve_build_directory(
328-
build_dir_list=cmake_args.build_dir,
329-
automatic_discovery=cmake_args.automatic_discovery,
330-
)
331-
332-
if cmake_args.detect_configured_files and self.build_dir:
333-
self.cmake_trace_log = self.build_dir / self.DEFAULT_TRACE_LOG
334-
301+
def _process_keyword_cmake_args(self, cmake_args):
302+
"""Process keyword-style CMake arguments."""
335303
keyword_args = {
336304
'defines': ([], '-D{}'),
337305
'undefines': ([], '-U{}'),
@@ -352,6 +320,8 @@ def setup_cmake_args(self, cmake_args): # noqa: C901
352320
for element in value:
353321
self.cmake_args.append(format_str.format(element))
354322

323+
def _process_flag_cmake_args(self, cmake_args):
324+
"""Process flag-style CMake arguments."""
355325
flag_args = {
356326
'dev_warnings': (False, '-Wdev'),
357327
'no_dev_warnings': (False, '-Wno_dev'),
@@ -360,18 +330,58 @@ def setup_cmake_args(self, cmake_args): # noqa: C901
360330
if getattr(cmake_args, key, default):
361331
self.cmake_args.append(flag_str)
362332

363-
platform_args = {
333+
def _process_platform_cmake_args(self, cmake_args):
334+
"""Process platform-specific CMake arguments."""
335+
platform_args_config = {
364336
'linux': ([], 'Linux'),
365337
'mac': ([], 'Darwin'),
366338
'win': ([], 'Windows'),
367339
}
368340

369-
for key, (default, platform_name) in platform_args.items():
370-
platform_args = getattr(cmake_args, key, default)
371-
if platform.system() == platform_name and platform_args:
372-
for arg in platform_args:
341+
for key, (default, platform_name) in platform_args_config.items():
342+
platform_arg_values = getattr(cmake_args, key, default)
343+
if platform.system() == platform_name and platform_arg_values:
344+
for arg in platform_arg_values:
373345
self.cmake_args.append(arg.strip('"\''))
374346

347+
def setup_cmake_args(self, cmake_args):
348+
"""
349+
Setup CMake arguments.
350+
351+
Args:
352+
cmake_args: Dictionary-like data structure with following keys:
353+
- 'defines': list[str]
354+
- 'undefined': list[str]
355+
- 'errors': list[str]
356+
- 'no_errors': list[str]
357+
- 'generator': str
358+
- 'toolset': str
359+
- 'platform': str
360+
- 'preset': str
361+
- 'cmake': str
362+
- 'dev_warnings': bool
363+
- 'no_dev_warnings': bool
364+
- 'linux': list[str]
365+
- 'mac': list[str]
366+
- 'win': list[str]
367+
"""
368+
self.source_dir = Path(cmake_args.source_dir).resolve()
369+
if cmake_args.cmake:
370+
self.command = [Path(cmake_args.cmake).resolve()]
371+
self.no_cmake_configure = cmake_args.no_cmake_configure
372+
373+
self.resolve_build_directory(
374+
build_dir_list=cmake_args.build_dir,
375+
automatic_discovery=cmake_args.automatic_discovery,
376+
)
377+
378+
if cmake_args.detect_configured_files and self.build_dir:
379+
self.cmake_trace_log = self.build_dir / self.DEFAULT_TRACE_LOG
380+
381+
self._process_keyword_cmake_args(cmake_args)
382+
self._process_flag_cmake_args(cmake_args)
383+
self._process_platform_cmake_args(cmake_args)
384+
375385
def configure(self, command, *, clean_build=False):
376386
"""
377387
Run a CMake configure step (multi-process safe).

tests/python/_cmake_test.py

Lines changed: 57 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -164,20 +164,27 @@ def test_resolve_build_directory(tmp_path, dir_list, build_dir_tree, ref_path):
164164
assert cmake.build_dir == tmp_path / cmake.DEFAULT_BUILD_DIR if dir_list is None else dir_list[0]
165165

166166

167-
@pytest.mark.parametrize('system', ['Linux', 'Darwin', 'Windows'])
168-
@pytest.mark.parametrize('no_cmake_configure', [False, True])
169-
def test_setup_cmake_args( # noqa: PLR0912, PLR0915, C901
170-
mocker, system, no_cmake_configure
171-
):
172-
original_system = platform.system()
173-
174-
def system_stub():
175-
return system
176-
177-
mocker.patch('platform.system', system_stub)
178-
179-
cmake = CMakeCommand()
180-
167+
def _assert_args_in_cmake_args(cmake_args, items, format_str='{}'):
168+
"""Helper to verify items are present in cmake_args."""
169+
for item in items:
170+
expected = format_str.format(item)
171+
assert any(expected in arg for arg in cmake_args), f'Expected {expected} in cmake_args'
172+
173+
174+
def _assert_platform_args(cmake_args, args):
175+
"""Helper to verify platform-specific args."""
176+
platform_config = {
177+
'Linux': args.linux,
178+
'Darwin': args.mac,
179+
'Windows': args.win,
180+
}
181+
current_platform = platform.system()
182+
if current_platform in platform_config:
183+
_assert_args_in_cmake_args(cmake_args, platform_config[current_platform])
184+
185+
186+
def _create_test_args(original_system, no_cmake_configure):
187+
"""Create test arguments namespace."""
181188
args = argparse.Namespace()
182189
args.detect_configured_files = True
183190
args.no_cmake_configure = no_cmake_configure
@@ -202,6 +209,17 @@ def system_stub():
202209
args.linux = ['LNX_A', 'LNX_B']
203210
args.mac = ['MAC_A', 'MAC_B']
204211
args.win = ['WIN_A', 'WIN_B']
212+
return args
213+
214+
215+
@pytest.mark.parametrize('system', ['Linux', 'Darwin', 'Windows'])
216+
@pytest.mark.parametrize('no_cmake_configure', [False, True])
217+
def test_setup_cmake_args(mocker, system, no_cmake_configure):
218+
original_system = platform.system()
219+
mocker.patch('platform.system', lambda: system)
220+
221+
cmake = CMakeCommand()
222+
args = _create_test_args(original_system, no_cmake_configure)
205223

206224
assert cmake.source_dir is None
207225
assert cmake.build_dir is None
@@ -220,32 +238,18 @@ def system_stub():
220238
assert '-GNinja' in cmake.cmake_args
221239
assert '-A64' in cmake.cmake_args
222240
assert '-Tclang-toolset' in cmake.cmake_args
223-
224241
assert '-Wdev' in cmake.cmake_args
225242
assert '-Wno_dev' in cmake.cmake_args
226243

227244
# The arguments are not added to cmake.cmake_args since we only want to add them during a CMake configure call
228245
assert '--trace-expand' not in cmake.cmake_args
229246
assert '--trace-format=json-v1' not in cmake.cmake_args
230247

231-
for define in args.defines:
232-
assert any(define in arg for arg in cmake.cmake_args)
233-
for undefine in args.undefines:
234-
assert any(undefine in arg for arg in cmake.cmake_args)
235-
for error in args.errors:
236-
assert any(f'-Werror={error}' in arg for arg in cmake.cmake_args)
237-
for no_error in args.no_errors:
238-
assert any(f'-Wno-error={no_error}' in arg for arg in cmake.cmake_args)
239-
240-
if platform.system() == 'Linux':
241-
for linux in args.linux:
242-
assert any(linux in arg for arg in cmake.cmake_args)
243-
elif platform.system() == 'Darwin':
244-
for mac in args.mac:
245-
assert any(mac in arg for arg in cmake.cmake_args)
246-
elif platform.system() == 'Windows':
247-
for win in args.win:
248-
assert any(win in arg for arg in cmake.cmake_args)
248+
_assert_args_in_cmake_args(cmake.cmake_args, args.defines)
249+
_assert_args_in_cmake_args(cmake.cmake_args, args.undefines)
250+
_assert_args_in_cmake_args(cmake.cmake_args, args.errors, '-Werror={}')
251+
_assert_args_in_cmake_args(cmake.cmake_args, args.no_errors, '-Wno-error={}')
252+
_assert_platform_args(cmake.cmake_args, args)
249253

250254

251255
@pytest.mark.parametrize('detect_configured_files', [False, True], ids=['no_trace', 'w_trace'])
@@ -261,12 +265,10 @@ def test_configure_cmake( # noqa: PLR0917
261265
detect_configured_files,
262266
):
263267
sys_exit = mocker.patch('sys.exit')
264-
FileLock = mocker.MagicMock(filelock.FileLock) # noqa: N806
265-
mocker.patch(filelock_module_name, FileLock)
266-
InterProcessReaderWriterLock = mocker.MagicMock( # noqa: N806
267-
fasteners.InterProcessReaderWriterLock
268-
)
269-
mocker.patch(interprocess_rw_lock_module_name, InterProcessReaderWriterLock)
268+
mock_file_lock = mocker.MagicMock(filelock.FileLock)
269+
mocker.patch(filelock_module_name, mock_file_lock)
270+
mock_rw_lock = mocker.MagicMock(fasteners.InterProcessReaderWriterLock)
271+
mocker.patch(interprocess_rw_lock_module_name, mock_rw_lock)
270272
configure = mocker.Mock(return_value=returncode)
271273
mocker.patch(internal_cmake_configure_name, configure)
272274
parse_log = mocker.patch('cmake_pc_hooks._cmake.CMakeCommand._parse_cmake_trace_log')
@@ -289,17 +291,17 @@ def test_configure_cmake( # noqa: PLR0917
289291
# ----------------------------------
290292

291293
if no_cmake_configure:
292-
FileLock.assert_not_called()
293-
InterProcessReaderWriterLock.assert_not_called()
294+
mock_file_lock.assert_not_called()
295+
mock_rw_lock.assert_not_called()
294296
configure.assert_not_called()
295297
return
296298

297-
FileLock.assert_called_once_with(build_dir / '_cmake_configure_try_lock')
298-
InterProcessReaderWriterLock.assert_called_once_with(build_dir / '_cmake_configure_lock')
299+
mock_file_lock.assert_called_once_with(build_dir / '_cmake_configure_try_lock')
300+
mock_rw_lock.assert_called_once_with(build_dir / '_cmake_configure_lock')
299301
configure.assert_called_once_with(
300302
lock_files=(
301-
InterProcessReaderWriterLock.call_args[0][0],
302-
FileLock.call_args[0][0],
303+
mock_rw_lock.call_args[0][0],
304+
mock_file_lock.call_args[0][0],
303305
),
304306
clean_build=clean_build,
305307
)
@@ -324,14 +326,12 @@ def timeout(blocking): # noqa: ARG001
324326

325327
args = {'acquire.side_effect': timeout}
326328
file_lock = mocker.MagicMock(filelock.FileLock, **args)
327-
FileLock = mocker.MagicMock(filelock.FileLock, return_value=file_lock) # noqa: N806
328-
mocker.patch(filelock_module_name, FileLock)
329+
mock_file_lock = mocker.MagicMock(filelock.FileLock, return_value=file_lock)
330+
mocker.patch(filelock_module_name, mock_file_lock)
329331

330332
interprocess_lock = mocker.MagicMock()
331-
InterProcessReaderWriterLock = mocker.MagicMock( # noqa: N806
332-
return_value=interprocess_lock
333-
)
334-
mocker.patch(interprocess_rw_lock_module_name, InterProcessReaderWriterLock)
333+
mock_rw_lock = mocker.MagicMock(return_value=interprocess_lock)
334+
mocker.patch(interprocess_rw_lock_module_name, mock_rw_lock)
335335
configure = mocker.Mock(return_value=0)
336336
mocker.patch(internal_cmake_configure_name, configure)
337337

@@ -349,21 +349,19 @@ def timeout(blocking): # noqa: ARG001
349349

350350
# ----------------------------------
351351

352-
FileLock.assert_called_once()
353-
InterProcessReaderWriterLock.assert_called_once()
352+
mock_file_lock.assert_called_once()
353+
mock_rw_lock.assert_called_once()
354354
interprocess_lock.read_lock.assert_called_once()
355355
configure.assert_not_called()
356356

357357

358358
def test_configure_invalid(mocker):
359359
mocker.patch('sys.exit', side_effect=ExitError)
360360

361-
FileLock = mocker.MagicMock(filelock.FileLock) # noqa: N806
362-
mocker.patch(filelock_module_name, FileLock)
363-
InterProcessReaderWriterLock = mocker.MagicMock( # noqa: N806
364-
fasteners.InterProcessReaderWriterLock
365-
)
366-
mocker.patch(interprocess_rw_lock_module_name, InterProcessReaderWriterLock)
361+
mock_file_lock = mocker.MagicMock(filelock.FileLock)
362+
mocker.patch(filelock_module_name, mock_file_lock)
363+
mock_rw_lock = mocker.MagicMock(fasteners.InterProcessReaderWriterLock)
364+
mocker.patch(interprocess_rw_lock_module_name, mock_rw_lock)
367365
configure = mocker.Mock(return_value=1)
368366
mocker.patch(internal_cmake_configure_name, configure)
369367

0 commit comments

Comments
 (0)