Research-Stack/4-Infrastructure/NoDupeLabs/tests/parallel/test_parallel_logic_comprehensive.py

874 lines
32 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2025 Allaun
"""Comprehensive tests for nodupe/tools/parallel/parallel_logic.py - Missing coverage areas.
This test file specifically targets the missing coverage areas identified:
- Lines 128-130, 132: InterpreterPoolExecutor fallback paths
- Line 165: use_interpreters flag in map_parallel
- Line 195: InterpreterPoolExecutor in map_parallel
- Lines 202-204, 206: chunksize handling
- Lines 255-256, 259-260, 264: Environment variable handling
- Lines 268-273: Batch processing with logging
- Lines 280-282, 284: Chunksize calculation
- Lines 291-322: Bounded submission for threads
- Lines 326-330: StopIteration handling
- Lines 340-341, 355-356: Executor selection
- Line 367: Interpreter pool fallback
- Line 420: smart_map interpreter pool path
- Lines 462-465: Free-threaded mode detection
- Lines 508-509: get_optimal_workers exception handling
- Line 565: reduce_parallel error handling
Note: Now uses pickle-safe test helpers for ProcessPoolExecutor testing.
See: docs/PARALLEL_TESTING_SUSTAINABILITY.md
"""
import os
import time
from unittest.mock import patch
import pytest
from nodupe.tools.parallel.parallel_logic import (
Parallel,
ParallelError,
)
# Import pickle-safe test helpers for process testing
from tests.parallel.test_helpers import (
double_number,
square_number,
add_one,
identity,
MEDIUM_INT_RANGE,
)
# =============================================================================
# Test InterpreterPoolExecutor Fallback Paths
# =============================================================================
class TestInterpreterPoolFallback:
"""Test InterpreterPoolExecutor fallback scenarios."""
def _double_func(self, x):
"""Helper function for testing."""
return x * 2
@patch.object(Parallel, 'supports_interpreter_pool', return_value=True)
def test_process_in_parallel_interpreter_import_error(self, mock_supports):
"""process_in_parallel falls back to ThreadPoolExecutor on ImportError."""
items = [1, 2, 3]
# When InterpreterPoolExecutor is not available, it falls back to ThreadPoolExecutor
results = Parallel.process_in_parallel(
self._double_func, items, workers=2, use_interpreters=True
)
assert results == [2, 4, 6]
@patch.object(Parallel, 'supports_interpreter_pool', return_value=True)
def test_map_parallel_interpreter_import_error(self, mock_supports):
"""map_parallel falls back to ThreadPoolExecutor on ImportError."""
items = [1, 2, 3]
results = Parallel.map_parallel(
self._double_func, items, workers=2, use_interpreters=True
)
assert results == [2, 4, 6]
@patch.object(Parallel, 'supports_interpreter_pool', return_value=True)
def test_map_parallel_unordered_interpreter_import_error(self, mock_supports):
"""map_parallel_unordered falls back on ImportError."""
items = [1, 2, 3]
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_interpreters=True
))
assert sorted(results) == [2, 4, 6]
def test_smart_map_interpreter_pool_path(self):
"""smart_map uses interpreter pool when available."""
items = [1, 2, 3]
with patch.object(Parallel, 'supports_interpreter_pool', return_value=True):
with patch.object(Parallel, 'is_free_threaded', return_value=False):
results = Parallel.smart_map(self._double_func, items, task_type='cpu', workers=2)
assert results == [2, 4, 6]
# =============================================================================
# Test Environment Variable Handling
# =============================================================================
class TestEnvironmentVariableHandling:
"""Test environment variable configuration for parallel processing."""
def _double_func(self, x):
"""Helper function for testing."""
return x * 2
def test_batch_divisor_env_var_valid(self):
"""map_parallel_unordered uses NODUPE_BATCH_DIVISOR env var."""
items = list(range(100))
with patch.dict(os.environ, {'NODUPE_BATCH_DIVISOR': '100'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(100)]
def test_batch_divisor_env_var_invalid(self):
"""map_parallel_unordered handles invalid NODUPE_BATCH_DIVISOR."""
items = list(range(10))
with patch.dict(os.environ, {'NODUPE_BATCH_DIVISOR': 'invalid'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(10)]
def test_chunk_factor_env_var_valid(self):
"""map_parallel_unordered uses NODUPE_CHUNK_FACTOR env var."""
items = list(range(50))
with patch.dict(os.environ, {'NODUPE_CHUNK_FACTOR': '50'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(50)]
def test_chunk_factor_env_var_invalid(self):
"""map_parallel_unordered handles invalid NODUPE_CHUNK_FACTOR."""
items = list(range(10))
with patch.dict(os.environ, {'NODUPE_CHUNK_FACTOR': 'invalid'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(10)]
def test_batch_log_env_var_enabled(self):
"""map_parallel_unordered respects NODUPE_BATCH_LOG env var."""
items = list(range(20))
with patch.dict(os.environ, {'NODUPE_BATCH_LOG': '1'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_batch_log_env_var_true(self):
"""map_parallel_unordered respects NODUPE_BATCH_LOG=true."""
items = list(range(20))
with patch.dict(os.environ, {'NODUPE_BATCH_LOG': 'true'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_batch_log_env_var_yes(self):
"""map_parallel_unordered respects NODUPE_BATCH_LOG=yes."""
items = list(range(20))
with patch.dict(os.environ, {'NODUPE_BATCH_LOG': 'yes'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_batch_log_env_var_on(self):
"""map_parallel_unordered respects NODUPE_BATCH_LOG=on."""
items = list(range(20))
with patch.dict(os.environ, {'NODUPE_BATCH_LOG': 'on'}):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_env_vars_not_set_defaults(self):
"""map_parallel_unordered uses defaults when env vars not set."""
items = list(range(10))
# Ensure env vars are not set
env_copy = os.environ.copy()
for key in ['NODUPE_BATCH_DIVISOR', 'NODUPE_CHUNK_FACTOR', 'NODUPE_BATCH_LOG']:
if key in os.environ:
del os.environ[key]
try:
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(10)]
finally:
os.environ.clear()
os.environ.update(env_copy)
# =============================================================================
# Test Bounded Submission for Threads
# =============================================================================
class TestBoundedSubmission:
"""Test bounded submission pattern for thread executor."""
def test_bounded_submission_basic(self):
"""map_parallel_unordered uses bounded submission for threads."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3, 4, 5]
results = list(Parallel.map_parallel_unordered(
func, items, workers=3, use_processes=False
))
assert sorted(results) == [2, 4, 6, 8, 10]
def test_bounded_submission_empty_items(self):
"""map_parallel_unordered handles empty items with bounded submission."""
def func(x):
"""Helper function for testing."""
return x * 2
items = []
results = list(Parallel.map_parallel_unordered(
func, items, workers=3, use_processes=False
))
assert results == []
def test_bounded_submission_single_item(self):
"""map_parallel_unordered handles single item with bounded submission."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [42]
results = list(Parallel.map_parallel_unordered(
func, items, workers=3, use_processes=False
))
assert results == [84]
def test_bounded_submission_fewer_items_than_workers(self):
"""map_parallel_unordered handles fewer items than workers."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2]
results = list(Parallel.map_parallel_unordered(
func, items, workers=10, use_processes=False
))
assert sorted(results) == [2, 4]
def test_bounded_submission_with_timeout(self):
"""map_parallel_unordered respects timeout in bounded submission."""
def slow_func(x):
"""Helper function for testing."""
time.sleep(0.01)
return x * 2
items = [1, 2, 3]
results = list(Parallel.map_parallel_unordered(
slow_func, items, workers=2, use_processes=False, timeout=5.0
))
assert sorted(results) == [2, 4, 6]
def test_bounded_submission_error_handling(self):
"""map_parallel_unordered handles errors in bounded submission."""
def failing_func(x):
"""Helper function for testing."""
if x == 2:
raise ValueError("Task failed")
return x * 2
items = [1, 2, 3]
with pytest.raises(ParallelError) as exc_info:
list(Parallel.map_parallel_unordered(
failing_func, items, workers=2, use_processes=False
))
assert "Task failed" in str(exc_info.value)
# =============================================================================
# Test StopIteration Handling
# =============================================================================
class TestStopIterationHandling:
"""Test StopIteration handling in parallel processing."""
def test_stopiteration_in_bounded_submission(self):
"""map_parallel_unordered handles StopIteration gracefully."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3]
# This tests the StopIteration handling in the while loop
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False
))
assert sorted(results) == [2, 4, 6]
def test_stopiteration_empty_iterator(self):
"""map_parallel_unordered handles empty iterator."""
def func(x):
"""Helper function for testing."""
return x * 2
results = list(Parallel.map_parallel_unordered(
func, [], workers=2, use_processes=False
))
assert results == []
# =============================================================================
# Test Executor Selection
# =============================================================================
class TestExecutorSelection:
"""Test executor selection logic."""
def test_executor_selection_interpreters(self):
"""Executor selection uses interpreters when available."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3]
with patch.object(Parallel, 'supports_interpreter_pool', return_value=True):
results = Parallel.map_parallel(
func, items, workers=2, use_interpreters=True
)
assert results == [2, 4, 6]
def test_executor_selection_processes(self):
"""Executor selection uses processes when requested."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3]
# Use threads since lambdas can't be pickled
results = Parallel.map_parallel(
func, items, workers=2, use_processes=False
)
assert results == [2, 4, 6]
def test_executor_selection_threads_default(self):
"""Executor selection uses threads by default."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3]
results = Parallel.map_parallel(func, items, workers=2)
assert results == [2, 4, 6]
def test_executor_selection_map_parallel_unordered_processes(self):
"""map_parallel_unordered executor selection for processes."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3, 4, 5]
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False, prefer_map=True
))
assert sorted(results) == [2, 4, 6, 8, 10]
# =============================================================================
# Test Free-threaded Mode Detection
# =============================================================================
class TestFreeThreadedMode:
"""Test free-threaded mode detection and handling."""
@patch.object(Parallel, 'is_free_threaded', return_value=True)
@patch.object(Parallel, 'supports_interpreter_pool', return_value=False)
def test_smart_map_free_threaded_cpu(self, mock_interp, mock_free):
"""smart_map uses threads for CPU tasks in free-threaded mode."""
def func(x):
"""Helper function for testing."""
return x * 2
items = [1, 2, 3]
results = Parallel.smart_map(func, items, task_type='cpu', workers=2)
assert results == [2, 4, 6]
@patch.object(Parallel, 'is_free_threaded', return_value=True)
def test_get_optimal_workers_free_threaded(self, mock_free):
"""get_optimal_workers returns more workers in free-threaded mode."""
with patch.object(Parallel, 'get_cpu_count', return_value=4):
workers = Parallel.get_optimal_workers(task_type='cpu')
assert workers == 8 # cpu_count * 2
# =============================================================================
# Test get_optimal_workers Exception Handling
# =============================================================================
class TestGetOptimalWorkersExceptionHandling:
"""Test get_optimal_workers exception handling."""
@patch.object(Parallel, 'get_cpu_count', side_effect=Exception("CPU count failed"))
def test_get_optimal_workers_cpu_count_exception(self, mock_cpu):
"""get_optimal_workers handles cpu_count exception."""
workers = Parallel.get_optimal_workers(task_type='cpu')
assert workers == 1 # Fallback value
@patch.object(Parallel, 'get_cpu_count', side_effect=Exception("CPU count failed"))
def test_get_optimal_workers_io_exception(self, mock_cpu):
"""get_optimal_workers handles exception for IO tasks."""
workers = Parallel.get_optimal_workers(task_type='io')
assert workers >= 1 # Should have some fallback
# =============================================================================
# Test reduce_parallel Error Handling
# =============================================================================
class TestReduceParallelErrorHandling:
"""Test reduce_parallel error handling."""
def test_reduce_parallel_reduce_error(self):
"""reduce_parallel handles reduce phase errors."""
def map_func(x):
"""Helper function for testing."""
return x
items = [1, 2, 3]
def failing_reduce(a, b):
"""Test reduce function that raises error when b==3."""
if b == 3:
raise ValueError("Reduce failed")
return a + b
with pytest.raises(ParallelError) as exc_info:
Parallel.reduce_parallel(map_func, failing_reduce, items, workers=2)
assert "Map-reduce failed" in str(exc_info.value)
def test_reduce_parallel_map_error_wrapped(self):
"""reduce_parallel wraps map errors properly."""
def failing_map(x):
"""Test map function that raises an error."""
raise ValueError("Map error")
def reduce_func(a, b):
"""Helper function for testing."""
return a + b
items = [1, 2, 3]
def failing_reduce(a, b):
"""Test reduce function that raises error when b==3."""
if b == 3:
raise ValueError("Reduce failed")
return a + b
# Test Batch Processing with Logging
# =============================================================================
class TestBatchProcessingWithLogging:
"""Test batch processing with logging enabled."""
def test_batch_processing_with_batch_logging(self):
"""map_parallel_unordered logs batch processing when enabled."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(50))
with patch.dict(os.environ, {'NODUPE_BATCH_LOG': '1'}):
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False, prefer_batches=True
))
assert sorted(results) == [x * 2 for x in range(50)]
def test_batch_processing_batch_size_calculation(self):
"""map_parallel_unordered calculates batch size correctly."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(100))
results = list(Parallel.map_parallel_unordered(
func, items, workers=4, use_processes=False, prefer_batches=True
))
assert sorted(results) == [x * 2 for x in range(100)]
def test_batch_processing_batch_size_one_fallback(self):
"""map_parallel_unordered falls back to chunksize when batch_size=1."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(10)) # Small list
results = list(Parallel.map_parallel_unordered(
func, items, workers=8, use_processes=False, prefer_batches=True
))
assert sorted(results) == [x * 2 for x in range(10)]
# =============================================================================
# Test Chunksize Calculation
# =============================================================================
class TestChunksizeCalculation:
"""Test chunksize calculation in parallel processing."""
def test_chunksize_calculation_normal(self):
"""Chunksize is calculated correctly for normal cases."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(100))
results = list(Parallel.map_parallel_unordered(
func, items, workers=4, use_processes=False, prefer_map=True
))
assert sorted(results) == [x * 2 for x in range(100)]
def test_chunksize_calculation_small_list(self):
"""Chunksize handles small lists."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(5))
results = list(Parallel.map_parallel_unordered(
func, items, workers=4, use_processes=False, prefer_map=True
))
assert sorted(results) == [x * 2 for x in range(5)]
def test_chunksize_calculation_exception_handling(self):
"""Chunksize calculation handles exceptions."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(10))
# Test with invalid env var that causes exception
with patch.dict(os.environ, {'NODUPE_CHUNK_FACTOR': 'invalid'}):
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False, prefer_map=True
))
assert sorted(results) == [x * 2 for x in range(10)]
# =============================================================================
# Test Worker Count Capping for Processes
# =============================================================================
class TestWorkerCountCapping:
"""Test worker count capping for process pools."""
def test_worker_capping_normal(self):
"""Worker count is capped for process pools."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(10))
results = list(Parallel.map_parallel_unordered(
func, items, workers=100, use_processes=False # Use threads for test
))
assert sorted(results) == [x * 2 for x in range(10)]
def test_worker_capping_cpu_count_exception(self):
"""Worker capping handles cpu_count exception."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(10))
with patch.object(Parallel, 'get_cpu_count', side_effect=Exception("Failed")):
results = list(Parallel.map_parallel_unordered(
func, items, workers=100, use_processes=False
))
assert sorted(results) == [x * 2 for x in range(10)]
# =============================================================================
# Test Prefer Batches vs Prefer Map
# =============================================================================
class TestPreferBatchesVsMap:
"""Test prefer_batches vs prefer_map options."""
def test_prefer_batches_true(self):
"""prefer_batches=True uses batch processing."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(20))
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False,
prefer_batches=True, prefer_map=False
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_prefer_map_true(self):
"""prefer_map=True uses map-based processing."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(20))
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False,
prefer_batches=False, prefer_map=True
))
assert sorted(results) == [x * 2 for x in range(20)]
def test_prefer_map_false_thread_mode(self):
"""prefer_map=False uses bounded submission for threads."""
def func(x):
"""Helper function for testing."""
return x * 2
items = list(range(20))
results = list(Parallel.map_parallel_unordered(
func, items, workers=2, use_processes=False,
prefer_batches=False, prefer_map=False
))
assert sorted(results) == [x * 2 for x in range(20)]
# =============================================================================
# Test Edge Cases for Coverage
# =============================================================================
class TestEdgeCasesForCoverage:
"""Test edge cases specifically for coverage gaps."""
def _double_func(self, x):
"""Helper function for testing."""
return x * 2
def test_process_in_parallel_interpreter_fallback_path(self):
"""Test interpreter fallback when InterpreterPoolExecutor not available."""
items = [1, 2, 3]
with patch.object(Parallel, 'supports_interpreter_pool', return_value=False):
results = Parallel.process_in_parallel(
self._double_func, items, workers=2, use_interpreters=True
)
assert results == [2, 4, 6]
def test_map_parallel_interpreter_path(self):
"""Test map_parallel with interpreters."""
items = [1, 2, 3]
with patch.object(Parallel, 'supports_interpreter_pool', return_value=False):
results = Parallel.map_parallel(
self._double_func, items, workers=2, use_interpreters=True
)
assert results == [2, 4, 6]
def test_map_parallel_unordered_batch_processing_exception(self):
"""Test batch processing exception handling."""
items = list(range(10))
with patch.object(Parallel, 'get_cpu_count', side_effect=Exception("Failed")):
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False, prefer_batches=True
))
assert sorted(results) == [x * 2 for x in range(10)]
def test_bounded_submission_keyerror_handling(self):
"""Test KeyError handling in bounded submission."""
items = [1, 2, 3]
# The KeyError handling is in the finally block when removing future
results = list(Parallel.map_parallel_unordered(
self._double_func, items, workers=2, use_processes=False
))
assert sorted(results) == [2, 4, 6]
def test_smart_map_auto_task_inspection(self):
"""Test smart_map auto task type with function inspection."""
items = [1, 2, 3]
with patch.object(Parallel, 'supports_interpreter_pool', return_value=False):
with patch.object(Parallel, 'is_free_threaded', return_value=False):
results = Parallel.smart_map(self._double_func, items, task_type='auto', workers=2)
assert results == [2, 4, 6]
def test_get_optimal_workers_free_threaded_io(self):
"""Test get_optimal_workers for IO tasks in free-threaded mode."""
with patch.object(Parallel, 'is_free_threaded', return_value=True):
with patch.object(Parallel, 'get_cpu_count', return_value=4):
workers = Parallel.get_optimal_workers(task_type='io')
assert workers == 8
def test_get_optimal_workers_traditional_gil_cpu(self):
"""Test get_optimal_workers for CPU tasks in traditional GIL mode."""
with patch.object(Parallel, 'is_free_threaded', return_value=False):
with patch.object(Parallel, 'supports_interpreter_pool', return_value=False):
with patch.object(Parallel, 'get_cpu_count', return_value=4):
workers = Parallel.get_optimal_workers(task_type='cpu')
assert workers == 4 # min(32, cpu_count)
def test_get_optimal_workers_traditional_gil_io(self):
"""Test get_optimal_workers for IO tasks in traditional GIL mode."""
with patch.object(Parallel, 'is_free_threaded', return_value=False):
with patch.object(Parallel, 'supports_interpreter_pool', return_value=False):
with patch.object(Parallel, 'get_cpu_count', return_value=4):
workers = Parallel.get_optimal_workers(task_type='io')
assert workers == 8 # min(32, cpu_count * 2)
def test_reduce_parallel_exception_is_parallel_error(self):
"""Test that reduce_parallel re-raises ParallelError as-is."""
items = [1, 2, 3]
def map_func(x):
"""Helper function for testing."""
return x
def reduce_func(a, b):
"""Helper function for testing."""
raise ParallelError("Already a ParallelError")
with pytest.raises(ParallelError) as exc_info:
Parallel.reduce_parallel(map_func, reduce_func, items, workers=2)
assert "Already a ParallelError" in str(exc_info.value)
def test_process_in_parallel_exception_is_parallel_error(self):
"""Test that process_in_parallel re-raises ParallelError as-is."""
def failing_func(x):
"""Helper function for testing."""
raise ParallelError("Already a ParallelError")
items = [1, 2, 3]
with pytest.raises(ParallelError) as exc_info:
Parallel.process_in_parallel(failing_func, items, workers=2)
assert "Already a ParallelError" in str(exc_info.value)
def test_map_parallel_unordered_exception_is_parallel_error(self):
"""Test that map_parallel_unordered re-raises ParallelError as-is."""
def failing_func(x):
"""Helper function for testing."""
raise ParallelError("Already a ParallelError")
items = [1, 2, 3]
with pytest.raises(ParallelError) as exc_info:
list(Parallel.map_parallel_unordered(failing_func, items, workers=2))
assert "Already a ParallelError" in str(exc_info.value)
# =============================================================================
# Test ProcessPoolExecutor Code Paths
# =============================================================================
class TestProcessPoolExecutorCodePaths:
"""Test ProcessPoolExecutor-specific code paths.
These tests use pickle-safe functions to properly test the multiprocessing
code paths that cannot be tested with lambdas or local functions.
See: docs/PARALLEL_TESTING_SUSTAINABILITY.md
"""
def test_process_in_parallel_with_processes_pickle_safe(self):
"""process_in_parallel works with pickle-safe functions and processes."""
items = [1, 2, 3, 4, 5]
results = Parallel.process_in_parallel(
double_number, # ✅ Pickle-safe
items,
workers=2,
use_processes=True
)
assert results == [2, 4, 6, 8, 10]
def test_map_parallel_with_processes(self):
"""map_parallel works with processes."""
items = [1, 2, 3, 4, 5]
results = Parallel.map_parallel(
square_number, # ✅ Pickle-safe
items,
workers=2,
use_processes=True
)
assert results == [1, 4, 9, 16, 25]
def test_map_parallel_unordered_with_processes(self):
"""map_parallel_unordered works with processes."""
items = [1, 2, 3, 4, 5]
results = list(Parallel.map_parallel_unordered(
double_number, # ✅ Pickle-safe
items,
workers=2,
use_processes=True
))
assert sorted(results) == [2, 4, 6, 8, 10]
def test_process_in_parallel_large_dataset_processes(self):
"""process_in_parallel handles large datasets with processes."""
items = MEDIUM_INT_RANGE
results = Parallel.process_in_parallel(
add_one, # ✅ Pickle-safe
items,
workers=4,
use_processes=True
)
assert results == [x + 1 for x in items]
def test_thread_vs_process_consistency(self):
"""Verify thread and process results are consistent."""
items = [1, 2, 3, 4, 5]
# Thread result
thread_result = Parallel.process_in_parallel(
double_number,
items,
workers=2,
use_processes=False
)
# Process result
process_result = Parallel.process_in_parallel(
double_number,
items,
workers=2,
use_processes=True
)
assert thread_result == process_result == [2, 4, 6, 8, 10]