Skip to content

Commit b6ca292

Browse files
committed
Check outdated packages
1 parent 2fd8568 commit b6ca292

File tree

4 files changed

+110
-2
lines changed

4 files changed

+110
-2
lines changed

Makefile

+5-1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ build/%: ## build the latest image for a stack
4949
build-all: $(foreach I,$(ALL_IMAGES),arch_patch/$(I) build/$(I) ) ## build all stacks
5050
build-test-all: $(foreach I,$(ALL_IMAGES),arch_patch/$(I) build/$(I) test/$(I) ) ## build and test all stacks
5151

52+
check_outdated/%: ## Check the outdated conda packages in a stack and produce a report (experimental)
53+
@TEST_IMAGE="$(OWNER)/$(notdir $@)" pytest test/test_outdated.py
54+
5255
dev/%: ARGS?=
5356
dev/%: DARGS?=
5457
dev/%: PORT?=8888
@@ -89,4 +92,5 @@ tx-en: ## rebuild en locale strings and push to master (req: GH_TOKEN)
8992
@git push -u origin-tx master
9093

9194
test/%: ## run tests against a stack (only common tests or common tests + specific tests)
92-
@if [ ! -d "$(notdir $@)/test" ]; then TEST_IMAGE="$(OWNER)/$(notdir $@)" pytest test; else TEST_IMAGE="$(OWNER)/$(notdir $@)" pytest test $(notdir $@)/test; fi
95+
@if [ ! -d "$(notdir $@)/test" ]; then TEST_IMAGE="$(OWNER)/$(notdir $@)" pytest -m "not info" test; \
96+
else TEST_IMAGE="$(OWNER)/$(notdir $@)" pytest -m "not info" test $(notdir $@)/test; fi

pytest.ini

+3-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@
22
log_cli = 1
33
log_cli_level = INFO
44
log_cli_format = %(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)
5-
log_cli_date_format=%Y-%m-%d %H:%M:%S
5+
log_cli_date_format=%Y-%m-%d %H:%M:%S
6+
markers =
7+
info: marks tests as info (deselect with '-m "not info"')

requirements-dev.txt

+2
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ recommonmark==0.5.0
44
requests
55
sphinx>=1.6
66
sphinx-intl
7+
tabulate
78
transifex-client
9+

test/test_outdated.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
Based on the work https://oerpli.github.io/post/2019/06/conda-outdated/.
3+
See copyright below.
4+
5+
MIT License
6+
Copyright (c) 2019 Abraham Hinteregger
7+
Permission is hereby granted, free of charge, to any person obtaining a copy
8+
of this software and associated documentation files (the "Software"), to deal
9+
in the Software without restriction, including without limitation the rights
10+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
copies of the Software, and to permit persons to whom the Software is
12+
furnished to do so, subject to the following conditions:
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
22+
"""
23+
24+
import re
25+
import subprocess
26+
from collections import defaultdict
27+
from itertools import chain
28+
import logging
29+
30+
import pytest
31+
32+
from tabulate import tabulate
33+
34+
LOGGER = logging.getLogger(__name__)
35+
36+
37+
def get_versions(lines):
38+
"""Extract versions from the lines"""
39+
ddict = defaultdict(set)
40+
for line in lines.splitlines()[2:]:
41+
pkg, version = re.match(r"^(\S+)\s+(\S+)", line, re.MULTILINE).groups()
42+
ddict[pkg].add(version)
43+
return ddict
44+
45+
46+
def semantic_cmp(version_string):
47+
"""Manage semantic versioning for comparison"""
48+
49+
def mysplit(string):
50+
version_substrs = lambda x: re.findall(r"([A-z]+|\d+)", x)
51+
return list(chain(map(version_substrs, string.split("."))))
52+
53+
def str_ord(string):
54+
num = 0
55+
for char in string:
56+
num *= 255
57+
num += ord(char)
58+
return num
59+
60+
def try_int(version_str):
61+
try:
62+
return int(version_str)
63+
except ValueError:
64+
return str_ord(version_str)
65+
66+
mss = list(chain(*mysplit(version_string)))
67+
return tuple(map(try_int, mss))
68+
69+
70+
def print_result(installed, result):
71+
"""Print the result of the outdated check"""
72+
nb_packages = len(installed)
73+
nb_updatable = len(result)
74+
updatable_ratio = nb_updatable / nb_packages
75+
LOGGER.info(
76+
f"{nb_updatable} packages can be updated over {nb_packages} -> {updatable_ratio:.0%}"
77+
)
78+
LOGGER.info(f"\n{tabulate(result, headers='keys')}\n")
79+
80+
81+
@pytest.mark.info
82+
def test_outdated_packages(container):
83+
"""Getting the list of outdated packages"""
84+
LOGGER.info(f"Checking outdated packages in {container.image_name} ...")
85+
c = container.run(tty=True, command=["start.sh", "bash", "-c", "sleep infinity"])
86+
sp_i = c.exec_run(["conda", "list"])
87+
sp_v = c.exec_run(["conda", "search", "--outdated"])
88+
installed = get_versions(sp_i.output.decode("utf-8"))
89+
available = get_versions(sp_v.output.decode("utf-8"))
90+
result = list()
91+
for pkg, inst_vs in installed.items():
92+
avail_vs = sorted(list(available[pkg]), key=semantic_cmp)
93+
if not avail_vs:
94+
continue
95+
current = min(inst_vs, key=semantic_cmp)
96+
newest = avail_vs[-1]
97+
if avail_vs and current != newest:
98+
if semantic_cmp(current) < semantic_cmp(newest):
99+
result.append({"Package": pkg, "Current": current, "Newest": newest})
100+
print_result(installed, result)

0 commit comments

Comments
 (0)