Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PR: Check for asset availability when checking for updates #22598

Merged
merged 7 commits into from
Oct 16, 2024
63 changes: 52 additions & 11 deletions spyder/plugins/updatemanager/tests/test_update_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

import os
import logging
from packaging.version import parse

import pytest

from spyder.config.base import running_in_ci
from spyder.plugins.updatemanager import workers
from spyder.plugins.updatemanager.workers import WorkerUpdate, HTTP_ERROR_MSG
from spyder.plugins.updatemanager.workers import (
UpdateType, get_asset_info, WorkerUpdate, HTTP_ERROR_MSG
)
from spyder.plugins.updatemanager.widgets import update
from spyder.plugins.updatemanager.widgets.update import UpdateManagerWidget

Expand Down Expand Up @@ -49,7 +52,7 @@ def test_updates(qtbot, mocker, caplog, version, channel):
mocker.patch.object(
UpdateManagerWidget, "start_update", new=lambda x: None
)
mocker.patch.object(workers, "__version__", new=version)
mocker.patch.object(workers, "CURRENT_VERSION", new=parse(version))
mocker.patch.object(
workers, "get_spyder_conda_channel", return_value=channel
)
Expand Down Expand Up @@ -77,25 +80,63 @@ def test_updates(qtbot, mocker, caplog, version, channel):
assert update_available
else:
assert not update_available
assert len(um.update_worker.releases) >= 1


@pytest.mark.parametrize("release", ["4.0.1", "4.0.1a1"])
@pytest.mark.parametrize("release", ["6.0.0", "6.0.0b3"])
@pytest.mark.parametrize("version", ["4.0.0a1", "4.0.0"])
@pytest.mark.parametrize("stable_only", [True, False])
def test_update_non_stable(qtbot, mocker, version, release, stable_only):
"""Test we offer unstable updates."""
mocker.patch.object(workers, "__version__", new=version)
mocker.patch.object(workers, "CURRENT_VERSION", new=parse(version))

release = parse(release)
worker = WorkerUpdate(stable_only)
worker.releases = [release]
worker._check_update_available()
worker._check_update_available([release])

update_available = worker.update_available
if "a" in release and stable_only:
assert not update_available
if release.is_prerelease and stable_only:
assert not worker.update_available
else:
assert update_available
assert worker.update_available


@pytest.mark.parametrize("version", ["4.0.0", "6.0.0"])
def test_update_no_asset(qtbot, mocker, version):
"""Test update availability when asset is not available"""
mocker.patch.object(workers, "CURRENT_VERSION", new=parse(version))

releases = [parse("6.0.1"), parse("6.100.0")]
worker = WorkerUpdate(True)
worker._check_update_available(releases)

# For both values of version, there should be an update available
# However, the available version should be 6.0.1, since there is
# no asset for 6.100.0
assert worker.update_available
assert worker.latest_release == releases[0]


@pytest.mark.parametrize(
"release,update_type",
[
("6.0.1", UpdateType.Micro),
("6.1.0", UpdateType.Minor),
("7.0.0", UpdateType.Major)
]
)
@pytest.mark.parametrize("app", [True, False])
def test_get_asset_info(qtbot, mocker, release, update_type, app):
mocker.patch.object(workers, "CURRENT_VERSION", new=parse("6.0.0"))
mocker.patch.object(workers, "is_conda_based_app", return_value=app)

info = get_asset_info(release)
assert info['update_type'] == update_type

if update_type == "major" or not app:
assert info['url'].endswith(('.exe', '.pkg', '.sh'))
assert info['filename'].endswith(('.exe', '.pkg', '.sh'))
else:
assert info['url'].endswith(".zip")
assert info['filename'].endswith(".zip")


# ---- Test WorkerDownloadInstaller
Expand Down
49 changes: 16 additions & 33 deletions spyder/plugins/updatemanager/widgets/update.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@
import logging
import os
import os.path as osp
import platform
import shutil
import subprocess
import sys
from sysconfig import get_path

# Third-party imports
from packaging.version import parse
from qtpy.QtCore import Qt, QThread, QTimer, Signal
from qtpy.QtWidgets import QMessageBox, QWidget, QProgressBar, QPushButton
from spyder_kernels.utils.pythonenv import is_conda_env
Expand All @@ -28,6 +26,7 @@
from spyder.api.translations import _
from spyder.config.base import is_conda_based_app
from spyder.plugins.updatemanager.workers import (
get_asset_info,
WorkerUpdate,
WorkerDownloadInstaller
)
Expand Down Expand Up @@ -149,7 +148,7 @@ def __init__(self, parent):

def set_status(self, status=NO_STATUS):
"""Set the update manager status."""
self.sig_set_status.emit(status, self.latest_release)
self.sig_set_status.emit(status, str(self.latest_release))

def cleanup_threads(self):
"""Clean up QThreads"""
Expand Down Expand Up @@ -251,28 +250,11 @@ def _process_check_update(self):

def _set_installer_path(self):
"""Set the temp file path for the downloaded installer."""
if parse(__version__).major < parse(self.latest_release).major:
self.update_type = 'major'
elif parse(__version__).minor < parse(self.latest_release).minor:
self.update_type = 'minor'
else:
self.update_type = 'micro'

mach = platform.machine().lower().replace("amd64", "x86_64")

if self.update_type == 'major' or not is_conda_based_app():
if os.name == 'nt':
plat, ext = 'Windows', 'exe'
if sys.platform == 'darwin':
plat, ext = 'macOS', 'pkg'
if sys.platform.startswith('linux'):
plat, ext = 'Linux', 'sh'
fname = f'Spyder-{plat}-{mach}.{ext}'
else:
fname = 'spyder-conda-lock.zip'
asset_info = get_asset_info(self.latest_release)
self.update_type = asset_info['update_type']

dirname = osp.join(get_temp_dir(), 'updates', self.latest_release)
self.installer_path = osp.join(dirname, fname)
dirname = osp.join(get_temp_dir(), 'updates', str(self.latest_release))
self.installer_path = osp.join(dirname, asset_info['filename'])
self.installer_size_path = osp.join(dirname, "size")

logger.info(f"Update type: {self.update_type}")
Expand Down Expand Up @@ -472,14 +454,15 @@ def start_install(self):
if os.name == 'nt':
cmd = ['start', '"Update Spyder"'] + sub_cmd
elif sys.platform == 'darwin':
# Terminal cannot accept a command with arguments therefore
# create a temporary script
tmpscript = osp.join(get_temp_dir(), 'tmp_install.sh')
with open(tmpscript, 'w') as f:
f.write(' '.join(sub_cmd))
os.chmod(tmpscript, 0o711) # set executable permissions

cmd = ['open', '-b', 'com.apple.terminal', tmpscript]
# Terminal cannot accept a command with arguments. Creating a
# wrapper script pollutes the shell history. Best option is to
# use osascript
sub_cmd_str = ' '.join(sub_cmd)
cmd = [
"osascript", "-e",
("""'tell application "Terminal" to do script"""
f""" "set +o history; {sub_cmd_str}; exit;"'"""),
]
else:
programs = [
{'cmd': 'gnome-terminal', 'exe-opt': '--window --'},
Expand Down Expand Up @@ -629,7 +612,7 @@ def manual_update_messagebox(parent, latest_release, channel):
).format(dont_mix_pip_conda_video)
else:
if channel == 'pkgs/main':
channel = ''
channel = '-c defaults'
else:
channel = f'-c {channel}'

Expand Down
Loading
Loading