Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

<!-- towncrier release notes start -->

## 0.27.1 (2025-08-06)
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version number in CHANGELOG.md (0.27.1) is inconsistent with the version mentioned in the PR description (0.25.4). This version mismatch could cause confusion and should be aligned with the actual version being released.

Suggested change
## 0.27.1 (2025-08-06)
## 0.25.4 (2025-08-06)

Copilot uses AI. Check for mistakes.

### Bug Fixes

- Fixed error handling issues around pickle file load

## 0.27.0 (2025-08-03)

### Improvements

- Enhanced webhook event processing with GroupQueue implementation
Expand Down Expand Up @@ -64,7 +72,6 @@ Improved resource cleanup and state management after processing

- Fixed dockerfile's ocean user argument position to be under the last FROM


## 0.25.2 (2025-07-13)

### Improvements
Expand Down
146 changes: 146 additions & 0 deletions port_ocean/tests/utils/test_ipc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import tempfile
from unittest.mock import patch
from port_ocean.utils.ipc import FileIPC


class TestFileIPCErrorHandling:
def test_save_and_load_normal_operation(self) -> None:
"""Test that normal save and load operations work correctly."""
with tempfile.TemporaryDirectory() as temp_dir:
# Create a simple IPC instance and manually set file path to temp dir
ipc = FileIPC("test_process", "test_name", default_return="default")
ipc.file_path = f"{temp_dir}/test_name.pkl"

# Save data
test_data = {"key": "value", "number": 42}
ipc.save(test_data)

# Load data
loaded_data = ipc.load()
assert loaded_data == test_data

def test_load_missing_file_returns_default(self) -> None:
"""Test that loading a non-existent file returns the default value."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC(
"test_process", "test_name", default_return="default_value"
)
ipc.file_path = f"{temp_dir}/nonexistent.pkl"

result = ipc.load()
assert result == "default_value"

def test_load_corrupted_pickle_returns_default(self) -> None:
"""Test that loading a corrupted pickle file returns default value and logs warning."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC("test_process", "test_name", default_return="fallback")
ipc.file_path = f"{temp_dir}/corrupted.pkl"

# Create a corrupted pickle file
with open(ipc.file_path, "wb") as f:
f.write(b"corrupted pickle data")

with patch("port_ocean.utils.ipc.logger.warning") as mock_logger:
result = ipc.load()

assert result == "fallback"
mock_logger.assert_called_once()
assert "Failed to load IPC data" in str(mock_logger.call_args)

def test_load_truncated_pickle_returns_default(self) -> None:
"""Test that loading a truncated pickle file (EOFError) returns default value."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC("test_process", "test_name", default_return=[])
ipc.file_path = f"{temp_dir}/truncated.pkl"

# Create a truncated pickle file (empty file)
with open(ipc.file_path, "wb"):
pass # Create empty file

with patch("port_ocean.utils.ipc.logger.warning") as mock_logger:
result = ipc.load()

assert result == []
mock_logger.assert_called_once()

def test_load_type_error_during_unpickling_returns_default(self) -> None:
"""Test that TypeError during unpickling (e.g., constructor mismatch) returns default value."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC(
"test_process", "test_name", default_return="type_error_fallback"
)
ipc.file_path = f"{temp_dir}/type_error.pkl"

# Create a dummy file so existence check passes
with open(ipc.file_path, "wb") as f:
f.write(b"dummy content")

# Mock pickle.load to raise TypeError (simulating constructor signature mismatch)
with patch(
"pickle.load",
side_effect=TypeError(
"KindNotImplementedException.__init__() missing 1 required positional argument: 'available_kinds'"
),
):
with patch("port_ocean.utils.ipc.logger.warning") as mock_logger:
result = ipc.load()

assert result == "type_error_fallback"
mock_logger.assert_called_once()
assert "KindNotImplementedException" in str(
mock_logger.call_args
)

def test_load_attribute_error_during_unpickling_returns_default(self) -> None:
"""Test that AttributeError during unpickling returns default value."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC(
"test_process", "test_name", default_return="attr_error_fallback"
)
ipc.file_path = f"{temp_dir}/attr_error.pkl"

# Create a dummy file so existence check passes
with open(ipc.file_path, "wb") as f:
f.write(b"dummy content")

# Mock pickle.load to raise AttributeError
with patch(
"pickle.load",
side_effect=AttributeError(
"module 'some_module' has no attribute 'SomeClass'"
),
):
with patch("port_ocean.utils.ipc.logger.warning") as mock_logger:
result = ipc.load()

assert result == "attr_error_fallback"
mock_logger.assert_called_once()

def test_load_import_error_during_unpickling_returns_default(self) -> None:
"""Test that ImportError during unpickling returns default value."""
with tempfile.TemporaryDirectory() as temp_dir:
with patch("port_ocean.utils.ipc.os.makedirs"):
ipc = FileIPC(
"test_process", "test_name", default_return="import_error_fallback"
)
ipc.file_path = f"{temp_dir}/import_error.pkl"

# Create a dummy file so existence check passes
with open(ipc.file_path, "wb") as f:
f.write(b"dummy content")

# Mock pickle.load to raise ImportError
with patch(
"pickle.load",
side_effect=ImportError("No module named 'missing_module'"),
):
with patch("port_ocean.utils.ipc.logger.warning") as mock_logger:
result = ipc.load()

assert result == "import_error_fallback"
mock_logger.assert_called_once()
19 changes: 17 additions & 2 deletions port_ocean/utils/ipc.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pickle
import os
from typing import Any
from loguru import logger


class FileIPC:
Expand All @@ -22,8 +23,22 @@ def save(self, object: Any) -> None:
def load(self) -> Any:
if not os.path.exists(self.file_path):
return self.default_return
with open(self.file_path, "rb") as f:
return pickle.load(f)

try:
with open(self.file_path, "rb") as f:
return pickle.load(f)
except (
pickle.PickleError,
EOFError,
OSError,
TypeError,
AttributeError,
ImportError,
) as e:
logger.warning(
f"Failed to load IPC data from {self.file_path}: {str(e)}. Returning default value."
)
return self.default_return
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's raise a more indicative error and not return the self.default_return here


def delete(self) -> None:
if os.path.exists(self.file_path):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "port-ocean"
version = "0.27.0"
version = "0.27.1"
Copy link

Copilot AI Aug 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The version number in pyproject.toml (0.27.1) is inconsistent with the version mentioned in the PR description and CHANGELOG.md (0.25.4). This version mismatch could cause confusion and deployment issues.

Suggested change
version = "0.27.1"
version = "0.25.4"

Copilot uses AI. Check for mistakes.
description = "Port Ocean is a CLI tool for managing your Port projects."
readme = "README.md"
homepage = "https://app.getport.io"
Expand Down