pyappify 1.0.2__tar.gz → 1.0.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyappify-1.0.2 → pyappify-1.0.3}/PKG-INFO +1 -1
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify/__init__.py +146 -104
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/PKG-INFO +1 -1
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/SOURCES.txt +3 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyproject.toml +2 -2
- pyappify-1.0.3/tests/TestUpdateEnv.py +94 -0
- pyappify-1.0.3/tests/TestUpgrade.py +110 -0
- pyappify-1.0.3/tests/TestUpgradeOnline.py +21 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify/main.py +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/dependency_links.txt +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/entry_points.txt +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/requires.txt +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/pyappify.egg-info/top_level.txt +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/setup.cfg +0 -0
- {pyappify-1.0.2 → pyappify-1.0.3}/tests/TestVersion.py +0 -0
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
# pyappify/__init__.py
|
|
2
|
-
import os
|
|
3
|
-
import signal
|
|
4
|
-
import hashlib
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
import os
|
|
3
|
+
import signal
|
|
4
|
+
import hashlib
|
|
5
|
+
import json
|
|
6
|
+
import logging
|
|
7
|
+
import shutil
|
|
8
|
+
import urllib.request
|
|
9
|
+
import zipfile
|
|
10
|
+
import threading
|
|
11
|
+
|
|
12
|
+
app_version = os.environ.get("PYAPPIFY_APP_VERSION")
|
|
13
|
+
app_starting_version = os.environ.get("PYAPPIFY_APP_STARTING_VERSION") or app_version
|
|
14
|
+
update_note = os.environ.get("PYAPPIFY_UPDATE_NOTE")
|
|
15
|
+
app_profile = os.environ.get("PYAPPIFY_APP_PROFILE")
|
|
16
|
+
pyappify_version = os.environ.get("PYAPPIFY_VERSION")
|
|
17
|
+
pyappify_executable = os.environ.get("PYAPPIFY_EXECUTABLE")
|
|
18
|
+
|
|
19
|
+
pyappify_upgradeable = os.environ.get("PYAPPIFY_UPGRADEABLE") == '1'
|
|
20
|
+
logger = None
|
|
21
|
+
_console_logger = None
|
|
17
22
|
|
|
18
23
|
try:
|
|
19
24
|
pid = int(os.environ.get("PYAPPIFY_PID"))
|
|
@@ -24,11 +29,27 @@ import sys
|
|
|
24
29
|
|
|
25
30
|
try:
|
|
26
31
|
import ctypes
|
|
27
|
-
except ImportError:
|
|
28
|
-
ctypes = None
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def
|
|
32
|
+
except ImportError:
|
|
33
|
+
ctypes = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _get_logger():
|
|
37
|
+
global _console_logger
|
|
38
|
+
|
|
39
|
+
if logger is not None:
|
|
40
|
+
return logger
|
|
41
|
+
if _console_logger is None:
|
|
42
|
+
_console_logger = logging.getLogger("pyappify")
|
|
43
|
+
if not _console_logger.handlers:
|
|
44
|
+
handler = logging.StreamHandler()
|
|
45
|
+
handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
|
|
46
|
+
_console_logger.addHandler(handler)
|
|
47
|
+
_console_logger.setLevel(logging.INFO)
|
|
48
|
+
_console_logger.propagate = False
|
|
49
|
+
return _console_logger
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def minimize_window_by_pid(pid):
|
|
32
53
|
if not ctypes or sys.platform != "win32":
|
|
33
54
|
return False
|
|
34
55
|
|
|
@@ -51,71 +72,63 @@ def minimize_window_by_pid(pid):
|
|
|
51
72
|
|
|
52
73
|
return False
|
|
53
74
|
|
|
54
|
-
def kill_pyappify():
|
|
55
|
-
if pid:
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
try:
|
|
59
|
-
os.kill(pid, signal.SIGTERM)
|
|
60
|
-
except Exception as e:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def _do_upgrade():
|
|
85
|
-
tmp_dir = os.path.join(os.getcwd(), "pyappify_tmp")
|
|
86
|
-
try:
|
|
75
|
+
def kill_pyappify():
|
|
76
|
+
if pid:
|
|
77
|
+
log = _get_logger()
|
|
78
|
+
log.info(f"Attempting to terminate process with PID: {pid}")
|
|
79
|
+
try:
|
|
80
|
+
os.kill(pid, signal.SIGTERM)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
log.error(f"Failed to terminate process with PID {pid}: {e}")
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def hide_pyappify():
|
|
86
|
+
if pid:
|
|
87
|
+
log = _get_logger()
|
|
88
|
+
log.info(f"Attempting to minimize window for process with PID: {pid}")
|
|
89
|
+
try:
|
|
90
|
+
minimize_window_by_pid(pid)
|
|
91
|
+
except Exception as e:
|
|
92
|
+
log.error(f"Failed to minimize window for process with PID {pid}: {e}")
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
def upgrade(to_version, executable_sha256, executable_zip_urls, stop_event=None):
|
|
96
|
+
log = _get_logger()
|
|
97
|
+
if not pyappify_upgradeable or not is_greater_version(to_version, pyappify_version):
|
|
98
|
+
log.info(f"pyappify no need to upgrade {pyappify_upgradeable} {to_version} {executable_sha256} {executable_zip_urls}")
|
|
99
|
+
return
|
|
100
|
+
log.info(
|
|
101
|
+
f"pyappify start to upgrade {pyappify_upgradeable} {to_version} {executable_sha256} {executable_zip_urls}")
|
|
102
|
+
def _do_upgrade():
|
|
103
|
+
tmp_dir = os.path.join(os.getcwd(), "pyappify_tmp")
|
|
104
|
+
try:
|
|
87
105
|
os.makedirs(tmp_dir, exist_ok=True)
|
|
88
|
-
downloaded_zip_path = None
|
|
89
|
-
for url in executable_zip_urls:
|
|
90
|
-
try:
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
logger.info("pyappify Upgrade download cancelled by stop event.")
|
|
100
|
-
return
|
|
106
|
+
downloaded_zip_path = None
|
|
107
|
+
for url in executable_zip_urls:
|
|
108
|
+
try:
|
|
109
|
+
log.info(
|
|
110
|
+
f"pyappify start to download {url}")
|
|
111
|
+
local_zip_path = os.path.join(tmp_dir, os.path.basename(url))
|
|
112
|
+
with urllib.request.urlopen(url) as response, open(local_zip_path, 'wb') as out_file:
|
|
113
|
+
while True:
|
|
114
|
+
if stop_event and stop_event.is_set():
|
|
115
|
+
log.info("pyappify Upgrade download cancelled by stop event.")
|
|
116
|
+
return
|
|
101
117
|
chunk = response.read(8192)
|
|
102
118
|
if not chunk:
|
|
103
119
|
break
|
|
104
120
|
out_file.write(chunk)
|
|
105
|
-
downloaded_zip_path = local_zip_path
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if logger:
|
|
117
|
-
logger.error("pyappify Failed to download upgrade.")
|
|
118
|
-
return
|
|
121
|
+
downloaded_zip_path = local_zip_path
|
|
122
|
+
log.info(
|
|
123
|
+
f"pyappify download success {url}")
|
|
124
|
+
break
|
|
125
|
+
except Exception as e:
|
|
126
|
+
log.warning(f"pyappify Failed to download from {url}: {e}")
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
if not downloaded_zip_path:
|
|
130
|
+
log.error("pyappify Failed to download upgrade.")
|
|
131
|
+
return
|
|
119
132
|
|
|
120
133
|
with zipfile.ZipFile(downloaded_zip_path, 'r') as zip_ref:
|
|
121
134
|
zip_ref.extractall(tmp_dir)
|
|
@@ -127,39 +140,68 @@ def upgrade(to_version, executable_sha256, executable_zip_urls, stop_event=None)
|
|
|
127
140
|
found_executable_path = os.path.join(root, new_executable_name)
|
|
128
141
|
break
|
|
129
142
|
|
|
130
|
-
if not found_executable_path:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
143
|
+
if not found_executable_path:
|
|
144
|
+
log.error("pyappify Executable not found in zip.")
|
|
145
|
+
return
|
|
134
146
|
|
|
135
147
|
sha256_hash = hashlib.sha256()
|
|
136
148
|
with open(found_executable_path, "rb") as f:
|
|
137
149
|
for byte_block in iter(lambda: f.read(4096), b""):
|
|
138
150
|
sha256_hash.update(byte_block)
|
|
139
151
|
|
|
140
|
-
if executable_sha256 and sha256_hash.hexdigest() != executable_sha256:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
except Exception as e:
|
|
150
|
-
if logger:
|
|
151
|
-
logger.error(f"pyappify Upgrade failed: {e}")
|
|
152
|
+
if executable_sha256 and sha256_hash.hexdigest() != executable_sha256:
|
|
153
|
+
log.error("pyappify SHA256 checksum mismatch.")
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
kill_pyappify()
|
|
157
|
+
shutil.move(found_executable_path, pyappify_executable)
|
|
158
|
+
log.info(f"pyappify Upgrade success")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
log.error(f"pyappify Upgrade failed: {e}")
|
|
152
161
|
finally:
|
|
153
162
|
if os.path.exists(tmp_dir):
|
|
154
163
|
shutil.rmtree(tmp_dir)
|
|
155
164
|
|
|
156
|
-
thread = threading.Thread(target=_do_upgrade)
|
|
157
|
-
thread.daemon = True
|
|
158
|
-
thread.start()
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
165
|
+
thread = threading.Thread(target=_do_upgrade)
|
|
166
|
+
thread.daemon = True
|
|
167
|
+
thread.start()
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def is_app_updated():
|
|
171
|
+
return is_greater_version(app_version, app_starting_version)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def is_app_downgraded():
|
|
175
|
+
return is_greater_version(app_starting_version, app_version)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def is_updated():
|
|
179
|
+
return is_app_updated()
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def is_downgrade():
|
|
183
|
+
return is_app_downgraded()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def get_update_notes():
|
|
187
|
+
if not update_note:
|
|
188
|
+
return []
|
|
189
|
+
try:
|
|
190
|
+
notes = json.loads(update_note)
|
|
191
|
+
except (TypeError, ValueError):
|
|
192
|
+
return []
|
|
193
|
+
if isinstance(notes, list):
|
|
194
|
+
return [str(note) for note in notes]
|
|
195
|
+
return []
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def get_update_note():
|
|
199
|
+
return get_update_notes()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def is_greater_version(version1, version2):
|
|
203
|
+
"""
|
|
204
|
+
Compares two semantic version strings.
|
|
163
205
|
|
|
164
206
|
Args:
|
|
165
207
|
version1 (str): The first version string.
|
|
@@ -176,4 +218,4 @@ def is_greater_version(version1, version2):
|
|
|
176
218
|
v2_parts = [int(p) for p in version2.split('.')]
|
|
177
219
|
return v1_parts > v2_parts
|
|
178
220
|
except (ValueError, AttributeError):
|
|
179
|
-
return False
|
|
221
|
+
return False
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pyappify"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "My awesome Python application"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.7"
|
|
@@ -22,4 +22,4 @@ dev = ["twine", "build"]
|
|
|
22
22
|
|
|
23
23
|
[tool.setuptools.packages.find]
|
|
24
24
|
where = ["."]
|
|
25
|
-
include = ["pyappify"]
|
|
25
|
+
include = ["pyappify"]
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import importlib
|
|
2
|
+
import os
|
|
3
|
+
import unittest
|
|
4
|
+
|
|
5
|
+
import pyappify
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
ENV_KEYS = (
|
|
9
|
+
"PYAPPIFY_APP_VERSION",
|
|
10
|
+
"PYAPPIFY_APP_STARTING_VERSION",
|
|
11
|
+
"PYAPPIFY_UPDATE_NOTE",
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class TestUpdateEnv(unittest.TestCase):
|
|
16
|
+
def setUp(self):
|
|
17
|
+
self._saved_env = {key: os.environ.get(key) for key in ENV_KEYS}
|
|
18
|
+
|
|
19
|
+
def tearDown(self):
|
|
20
|
+
for key, value in self._saved_env.items():
|
|
21
|
+
if value is None:
|
|
22
|
+
os.environ.pop(key, None)
|
|
23
|
+
else:
|
|
24
|
+
os.environ[key] = value
|
|
25
|
+
importlib.reload(pyappify)
|
|
26
|
+
|
|
27
|
+
def _reload_with_env(self, **env):
|
|
28
|
+
for key in ENV_KEYS:
|
|
29
|
+
os.environ.pop(key, None)
|
|
30
|
+
for key, value in env.items():
|
|
31
|
+
os.environ[key] = value
|
|
32
|
+
return importlib.reload(pyappify)
|
|
33
|
+
|
|
34
|
+
def test_starting_version_falls_back_to_app_version(self):
|
|
35
|
+
module = self._reload_with_env(PYAPPIFY_APP_VERSION="1.2.3")
|
|
36
|
+
|
|
37
|
+
self.assertEqual("1.2.3", module.app_version)
|
|
38
|
+
self.assertEqual("1.2.3", module.app_starting_version)
|
|
39
|
+
self.assertFalse(module.is_app_updated())
|
|
40
|
+
self.assertFalse(module.is_updated())
|
|
41
|
+
self.assertFalse(module.is_app_downgraded())
|
|
42
|
+
self.assertFalse(module.is_downgrade())
|
|
43
|
+
|
|
44
|
+
def test_missing_versions_are_not_update_or_downgrade(self):
|
|
45
|
+
module = self._reload_with_env()
|
|
46
|
+
|
|
47
|
+
self.assertIsNone(module.app_version)
|
|
48
|
+
self.assertIsNone(module.app_starting_version)
|
|
49
|
+
self.assertFalse(module.is_app_updated())
|
|
50
|
+
self.assertFalse(module.is_app_downgraded())
|
|
51
|
+
|
|
52
|
+
def test_detects_update_and_downgrade(self):
|
|
53
|
+
module = self._reload_with_env(
|
|
54
|
+
PYAPPIFY_APP_VERSION="2.0.0",
|
|
55
|
+
PYAPPIFY_APP_STARTING_VERSION="1.0.0",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
self.assertTrue(module.is_app_updated())
|
|
59
|
+
self.assertTrue(module.is_updated())
|
|
60
|
+
self.assertFalse(module.is_app_downgraded())
|
|
61
|
+
self.assertFalse(module.is_downgrade())
|
|
62
|
+
|
|
63
|
+
module = self._reload_with_env(
|
|
64
|
+
PYAPPIFY_APP_VERSION="1.0.0",
|
|
65
|
+
PYAPPIFY_APP_STARTING_VERSION="2.0.0",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
self.assertFalse(module.is_app_updated())
|
|
69
|
+
self.assertFalse(module.is_updated())
|
|
70
|
+
self.assertTrue(module.is_app_downgraded())
|
|
71
|
+
self.assertTrue(module.is_downgrade())
|
|
72
|
+
|
|
73
|
+
def test_returns_update_notes_from_json_array(self):
|
|
74
|
+
module = self._reload_with_env(
|
|
75
|
+
PYAPPIFY_UPDATE_NOTE='["first change", "second change"]'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
self.assertEqual(["first change", "second change"], module.get_update_notes())
|
|
79
|
+
self.assertEqual(["first change", "second change"], module.get_update_note())
|
|
80
|
+
|
|
81
|
+
def test_invalid_or_missing_update_notes_return_empty_list(self):
|
|
82
|
+
self.assertEqual([], self._reload_with_env().get_update_notes())
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
[],
|
|
85
|
+
self._reload_with_env(PYAPPIFY_UPDATE_NOTE="not json").get_update_notes(),
|
|
86
|
+
)
|
|
87
|
+
self.assertEqual(
|
|
88
|
+
[],
|
|
89
|
+
self._reload_with_env(PYAPPIFY_UPDATE_NOTE='"single note"').get_update_notes(),
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if __name__ == "__main__":
|
|
94
|
+
unittest.main()
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import shutil
|
|
5
|
+
import subprocess
|
|
6
|
+
import tempfile
|
|
7
|
+
import time
|
|
8
|
+
import unittest
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import pyappify
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
EXECUTABLE_SHA256 = "50ef2557c193ca1f97c1e699bab3dad35b3966c9e8e8b860fb609bd5fb3861d1"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _sha256(path):
|
|
18
|
+
digest = hashlib.sha256()
|
|
19
|
+
with open(path, "rb") as file:
|
|
20
|
+
for block in iter(lambda: file.read(1024 * 1024), b""):
|
|
21
|
+
digest.update(block)
|
|
22
|
+
return digest.hexdigest()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def assert_upgrade_kills_running_executable_and_replaces_it(
|
|
26
|
+
test_case, zip_url, timeout_seconds
|
|
27
|
+
):
|
|
28
|
+
source_executable = Path(os.environ["WINDIR"]) / "System32" / "PING.EXE"
|
|
29
|
+
test_case.assertTrue(source_executable.exists(), "Windows PING.EXE is required")
|
|
30
|
+
|
|
31
|
+
old_cwd = os.getcwd()
|
|
32
|
+
old_upgradeable = pyappify.pyappify_upgradeable
|
|
33
|
+
old_version = pyappify.pyappify_version
|
|
34
|
+
old_executable = pyappify.pyappify_executable
|
|
35
|
+
old_pid = pyappify.pid
|
|
36
|
+
old_logger = pyappify.logger
|
|
37
|
+
|
|
38
|
+
with tempfile.TemporaryDirectory() as temporary_directory:
|
|
39
|
+
executable = Path(temporary_directory) / "ok-ww.exe"
|
|
40
|
+
upgrade_temporary_directory = Path(temporary_directory) / "pyappify_tmp"
|
|
41
|
+
shutil.copy2(source_executable, executable)
|
|
42
|
+
test_case.assertNotEqual(_sha256(executable), EXECUTABLE_SHA256)
|
|
43
|
+
|
|
44
|
+
process = subprocess.Popen(
|
|
45
|
+
[str(executable), "-t", "127.0.0.1"],
|
|
46
|
+
cwd=temporary_directory,
|
|
47
|
+
stdout=subprocess.DEVNULL,
|
|
48
|
+
stderr=subprocess.DEVNULL,
|
|
49
|
+
)
|
|
50
|
+
try:
|
|
51
|
+
time.sleep(1)
|
|
52
|
+
test_case.assertIsNone(
|
|
53
|
+
process.poll(), "the current executable exited before upgrade began"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
pyappify.pyappify_upgradeable = True
|
|
57
|
+
pyappify.pyappify_version = "v3.3.45"
|
|
58
|
+
pyappify.pyappify_executable = str(executable)
|
|
59
|
+
pyappify.pid = process.pid
|
|
60
|
+
pyappify.logger = logging.getLogger(__name__)
|
|
61
|
+
os.chdir(temporary_directory)
|
|
62
|
+
|
|
63
|
+
pyappify.upgrade("v3.3.46", EXECUTABLE_SHA256, [zip_url])
|
|
64
|
+
|
|
65
|
+
deadline = time.monotonic() + timeout_seconds
|
|
66
|
+
while time.monotonic() < deadline:
|
|
67
|
+
process_stopped = process.poll() is not None
|
|
68
|
+
upgraded = (
|
|
69
|
+
executable.exists()
|
|
70
|
+
and _sha256(executable) == EXECUTABLE_SHA256
|
|
71
|
+
)
|
|
72
|
+
update_finished = not upgrade_temporary_directory.exists()
|
|
73
|
+
if process_stopped and upgraded and update_finished:
|
|
74
|
+
break
|
|
75
|
+
time.sleep(0.1)
|
|
76
|
+
|
|
77
|
+
test_case.assertIsNotNone(
|
|
78
|
+
process.poll(), "upgrade did not stop the old process"
|
|
79
|
+
)
|
|
80
|
+
test_case.assertEqual(EXECUTABLE_SHA256, _sha256(executable))
|
|
81
|
+
finally:
|
|
82
|
+
os.chdir(old_cwd)
|
|
83
|
+
pyappify.pyappify_upgradeable = old_upgradeable
|
|
84
|
+
pyappify.pyappify_version = old_version
|
|
85
|
+
pyappify.pyappify_executable = old_executable
|
|
86
|
+
pyappify.pid = old_pid
|
|
87
|
+
pyappify.logger = old_logger
|
|
88
|
+
if process.poll() is None:
|
|
89
|
+
process.terminate()
|
|
90
|
+
try:
|
|
91
|
+
process.wait(timeout=5)
|
|
92
|
+
except subprocess.TimeoutExpired:
|
|
93
|
+
process.kill()
|
|
94
|
+
process.wait(timeout=5)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestUpgrade(unittest.TestCase):
|
|
98
|
+
LOCAL_ZIP = Path(__file__).with_name("ok-ww-win32.zip")
|
|
99
|
+
|
|
100
|
+
@unittest.skipUnless(os.name == "nt", "executable replacement test is Windows-only")
|
|
101
|
+
def test_upgrade_from_local_zip_kills_running_executable_and_replaces_it(self):
|
|
102
|
+
self.assertTrue(self.LOCAL_ZIP.exists(), "tests/ok-ww-win32.zip is required")
|
|
103
|
+
assert_upgrade_kills_running_executable_and_replaces_it(
|
|
104
|
+
self,
|
|
105
|
+
self.LOCAL_ZIP.as_uri(), timeout_seconds=30
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
unittest.main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
|
|
4
|
+
from tests.TestUpgrade import assert_upgrade_kills_running_executable_and_replaces_it
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TestUpgradeOnline(unittest.TestCase):
|
|
8
|
+
ZIP_URL = (
|
|
9
|
+
"https://github.com/ok-oldking/ok-wuthering-waves/"
|
|
10
|
+
"releases/download/v3.3.46/ok-ww-win32.zip"
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
@unittest.skipUnless(os.name == "nt", "executable replacement test is Windows-only")
|
|
14
|
+
def test_upgrade_from_online_zip_kills_running_executable_and_replaces_it(self):
|
|
15
|
+
assert_upgrade_kills_running_executable_and_replaces_it(
|
|
16
|
+
self, self.ZIP_URL, timeout_seconds=600
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|