Skip to content
Merged
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
14 changes: 10 additions & 4 deletions kc-compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,26 @@ def is_distro_supported(distro_name):
return distro_name in SUPPORTED_DISTROS


def is_compat():
url = 'http://patches.kernelcare.com/' + get_kernel_hash() + '/version'
def _check_url(url):
try:
urlopen(url)
return True
Comment on lines +92 to 95
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

_check_url() calls urlopen(url) but never closes the returned response object and uses the default (potentially unbounded) timeout. This can leak sockets/file descriptors and may cause the CLI to hang indefinitely on slow/stalled connections. Consider opening the response with an explicit timeout and ensuring it is closed (e.g., close in a finally block or use contextlib.closing).

Copilot uses AI. Check for mistakes.
except HTTPError as e:
if e.code == 404:
return False
else:
raise
raise
except URLError:
raise


def is_compat():
base = 'http://patches.kernelcare.com/' + get_kernel_hash()
for endpoint in ('/version', '/latest.v2', '/latest.v3'):
if _check_url(base + endpoint):
return True
return False


def myprint(silent, message):
if not silent:
print(message)
Expand Down
56 changes: 53 additions & 3 deletions test_kc_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,19 +85,69 @@ def test_is_distro_supported(self):
assert kc_compat.is_distro_supported('debian') == False


class TestCheckUrl:
@patch.object(kc_compat, 'urlopen')
def test_check_url_success(self, mock_urlopen):
mock_urlopen.return_value = MagicMock()
assert kc_compat._check_url('http://example.com') == True

@patch.object(kc_compat, 'urlopen')
def test_check_url_404(self, mock_urlopen):
mock_urlopen.side_effect = HTTPError(None, 404, 'Not Found', None, None)
assert kc_compat._check_url('http://example.com') == False

@patch.object(kc_compat, 'urlopen')
def test_check_url_500_raises(self, mock_urlopen):
mock_urlopen.side_effect = HTTPError(None, 500, 'Server Error', None, None)
with pytest.raises(HTTPError):
kc_compat._check_url('http://example.com')

@patch.object(kc_compat, 'urlopen')
def test_check_url_url_error_raises(self, mock_urlopen):
mock_urlopen.side_effect = URLError('Connection refused')
with pytest.raises(URLError):
kc_compat._check_url('http://example.com')


class TestIsCompat:
BASE = 'http://patches.kernelcare.com/abcdef123456'

@patch.object(kc_compat, 'get_kernel_hash', return_value='abcdef123456')
@patch.object(kc_compat, 'urlopen')
def test_is_compat_success(self, mock_urlopen, mock_hash):
def test_is_compat_version_hit(self, mock_urlopen, mock_hash):
mock_urlopen.return_value = MagicMock()
assert kc_compat.is_compat() == True
mock_urlopen.assert_called_once_with('http://patches.kernelcare.com/abcdef123456/version')
mock_urlopen.assert_called_once_with(self.BASE + '/version')

@patch.object(kc_compat, 'get_kernel_hash', return_value='abcdef123456')
@patch.object(kc_compat, 'urlopen')
def test_is_compat_fallback_to_v2(self, mock_urlopen, mock_hash):
mock_urlopen.side_effect = [
HTTPError(None, 404, 'Not Found', None, None),
MagicMock(),
]
assert kc_compat.is_compat() == True
assert mock_urlopen.call_count == 2
mock_urlopen.assert_called_with(self.BASE + '/latest.v2')

Comment on lines +124 to +132
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This test only asserts the last urlopen() call (assert_called_with) plus call_count; it doesn't verify that is_compat() actually tried the expected sequence (e.g., /version first, then /latest.v2). Using assert_has_calls with the full expected call list would make the fallback behavior harder to regress.

Copilot uses AI. Check for mistakes.
@patch.object(kc_compat, 'get_kernel_hash', return_value='abcdef123456')
@patch.object(kc_compat, 'urlopen')
def test_is_compat_fallback_to_v3(self, mock_urlopen, mock_hash):
mock_urlopen.side_effect = [
HTTPError(None, 404, 'Not Found', None, None),
HTTPError(None, 404, 'Not Found', None, None),
MagicMock(),
]
assert kc_compat.is_compat() == True
assert mock_urlopen.call_count == 3
mock_urlopen.assert_called_with(self.BASE + '/latest.v3')
Comment on lines +135 to +143
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Similar to the v2 fallback test, this only asserts the final urlopen() call and total call_count. Consider asserting the full call order (e.g., /version, /latest.v2, /latest.v3) to ensure the fallback chain is actually exercised as intended.

Copilot uses AI. Check for mistakes.

@patch.object(kc_compat, 'get_kernel_hash', return_value='abcdef123456')
@patch.object(kc_compat, 'urlopen')
def test_is_compat_404_error_returns_false(self, mock_urlopen, mock_hash):
def test_is_compat_all_404_returns_false(self, mock_urlopen, mock_hash):
mock_urlopen.side_effect = HTTPError(None, 404, 'Not Found', None, None)
assert kc_compat.is_compat() == False
assert mock_urlopen.call_count == 3
Comment on lines 145 to +150
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This asserts call_count==3, but doesn't validate which URLs were attempted. Adding an assert_has_calls for the three expected endpoints would better lock in the intended behavior when all endpoints return 404.

Copilot uses AI. Check for mistakes.

@patch.object(kc_compat, 'get_kernel_hash', return_value='abcdef123456')
@patch.object(kc_compat, 'urlopen')
Expand Down
Loading