diff --git a/komodo/symlink/sanity_check.py b/komodo/symlink/sanity_check.py index 8c3bce3b..eca0c423 100644 --- a/komodo/symlink/sanity_check.py +++ b/komodo/symlink/sanity_check.py @@ -97,6 +97,13 @@ def _get_root_nodes(link_dict): return keys.difference(values) +def suggest_missing_roots(link_dict): + input_roots = link_dict.get("root_links", []) + inferred_roots = _get_root_nodes(link_dict) + if set(input_roots) != inferred_roots: + return sorted(list(set(inferred_roots).difference(input_roots))) + return [] + def assert_root_nodes(link_dict): input_roots = link_dict["root_links"] inferred_roots = _get_root_nodes(link_dict) diff --git a/komodo/symlink/suggester/cli.py b/komodo/symlink/suggester/cli.py index 2d87b0f7..698c18e5 100755 --- a/komodo/symlink/suggester/cli.py +++ b/komodo/symlink/suggester/cli.py @@ -35,7 +35,7 @@ def _parse_args(): formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument("release", help="e.g. 2019.12.rc0-py38") - parser.add_argument("mode", help="stable,testing") + parser.add_argument("mode", help="stable,testing,deprecated") parser.add_argument("joburl", help="link to the job that triggered this") parser.add_argument("jobname", help="name of the job") parser.add_argument("--git-fork", help="git fork", default="equinor") diff --git a/komodo/symlink/suggester/configuration.py b/komodo/symlink/suggester/configuration.py index ecc40d11..3c53ab3f 100644 --- a/komodo/symlink/suggester/configuration.py +++ b/komodo/symlink/suggester/configuration.py @@ -2,12 +2,13 @@ from typing import List, Tuple from komodo.symlink.suggester.release import Release - +from komodo.symlink.sanity_check import suggest_missing_roots class Configuration: def __init__(self, conf) -> None: self.conf = conf self.links = conf["links"] + self.root_links = conf.get("root_links", []) def _month_alias_update_only(self, link, release): return self.links.get(link, None) == release.month_alias() @@ -48,7 +49,7 @@ def update(self, release, mode, python_versions: List[str]): self.links[link] = release.month_alias() else: self.links[link] = repr(release) - elif mode == "stable": + elif mode in {"stable", "deprecated"}: self.links[release.month_alias()] = repr(release) self.links[link] = release.month_alias() else: @@ -81,10 +82,19 @@ def update( configuration = Configuration.from_json(symlink_configuration) configuration.update(release, mode, python_versions) - new_json_str = configuration.to_json(json_kwargs) - old_json_str = json.dumps(json.loads(symlink_configuration), **json_kwargs) + missing_roots = suggest_missing_roots(json.loads(new_json_str)) + if missing_roots: + appended_roots = json.loads(new_json_str) + + for missing_root in missing_roots: + if "root_links" in appended_roots: + appended_roots['root_links'].append(missing_root) + + new_json_str = json.dumps(appended_roots, **json_kwargs) + + old_json_str = json.dumps(json.loads(symlink_configuration), **json_kwargs) configuration_changed = new_json_str != old_json_str return f"{new_json_str}\n", configuration_changed diff --git a/tests/test_sanity_check.py b/tests/test_sanity_check.py index bf446706..abc6f79c 100644 --- a/tests/test_sanity_check.py +++ b/tests/test_sanity_check.py @@ -1,7 +1,21 @@ import pytest from komodo.lint_symlink_config import lint_symlink_config -from komodo.symlink.sanity_check import assert_root_nodes +from komodo.symlink.sanity_check import assert_root_nodes, suggest_missing_roots + + +def test_suggest_missing_root_links(): + link_dict = { + "links": { + "stable": "2012.01", + "testing": "2012.03", + "deprecated-py38": "2011.11", + "deprecated-py311": "2011.12", + }, + "root_links": ["stable", "testing"], + } + + assert suggest_missing_roots(link_dict) == sorted(["deprecated-py311", "deprecated-py38"]) def test_assert_root_nodes_error_message_missing_roots(): diff --git a/tests/test_suggester.py b/tests/test_suggester.py index 8796b05b..c51792cd 100644 --- a/tests/test_suggester.py +++ b/tests/test_suggester.py @@ -3,6 +3,7 @@ from unittest.mock import ANY, MagicMock import pytest +import json from komodo.symlink.suggester.cli import suggest_symlink_configuration from komodo.symlink.suggester.configuration import Configuration, update @@ -233,6 +234,33 @@ def test_get_concrete_release(conf, link, concrete): "testing-py37": "2021.01-py37" } } +""", + ), + # suggest deprecated update to 2020.11-py37 + ( + """{"links": { + "2020.10-py37": "2020.10.02-py37", + "2020.11-py37": "2020.11.04-py37", + "2020.12-py37": "2020.12.rc2-py37", + "stable-py3": "stable-py37", + "stable-py37": "2020.11-py37", + "deprecated-py3": "deprecated-py37", + "deprecated-py37": "2020.10-py37" + }}""", + "2020.11.04-py37", + "deprecated", + "changed", + """{ + "links": { + "2020.10-py37": "2020.10.02-py37", + "2020.11-py37": "2020.11.04-py37", + "2020.12-py37": "2020.12.rc2-py37", + "deprecated-py3": "deprecated-py37", + "deprecated-py37": "2020.11-py37", + "stable-py3": "stable-py37", + "stable-py37": "2020.11-py37" + } +} """, ), ], @@ -255,14 +283,17 @@ def _mock_repo(sym_config): [ ("foo.json", "stable"), ("foo_azure.json", "stable"), + ("foo.json", "deprecated"), + ("foo.json", "testing"), ], ) def test_suggest_symlink_configuration(symlink_file, mode): """Testing whether when updating symlink file the branch gets a corresponding name.""" config = """{"links": { "2050.02-py58": "2050.02.00-py58", -"stable-py58": "2050.02-py58" -}}""" +"deprecated-py58": "2050.02-py58", +"stable-py58": "2050.02-py58", +"testing-py58": "2050.02-py58"}}""" repo = _mock_repo(config) args = Namespace( @@ -288,7 +319,9 @@ def test_suggest_symlink_configuration(symlink_file, mode): """{ "links": { "2050.02-py58": "2050.02.01-py58", - "stable-py58": "2050.02-py58" + "deprecated-py58": "2050.02-py58", + "stable-py58": "2050.02-py58", + "testing-py58": "2050.02-py58" } } """, @@ -306,8 +339,7 @@ def test_suggest_symlink_configuration(symlink_file, mode): def test_noop_suggestion(): config = """{"links": { "2050.02-py58": "2050.02.00-py58", -"stable-py58": "2050.02-py58" -}}""" +"stable-py58": "2050.02-py58"}}""" repo = _mock_repo(config) args = Namespace( @@ -321,15 +353,13 @@ def test_noop_suggestion(): ) repo.create_pull.assert_not_called() - assert suggest_symlink_configuration(args, repo) is None def test_suggest_symlink_multi_configuration(): config = """{"links": { "2050.02-py58": "2050.02.00-py58", -"stable-py58": "2050.02-py58" -}}""" +"stable-py58": "2050.02-py58"}}""" repo = _mock_repo(config) mode = "stable" args = Namespace( @@ -388,6 +418,36 @@ def test_suggest_symlink_multi_configuration(): "testing-py38": "2001.12.rc0-py38" } } +""", + ), + # testing deprecated promotion from previous release + ( + """{"links": { + "2001.11-py38": "2001.11.00-py38", + "2001.11-py311": "2001.11.00-py311", + "deprecated-py38": "2001.11.rc0-py38", + "deprecated-py311": "2001.11.rc0-py311", + "stable-py38" : "2001.11-py38", + "testing-py38": "2001.11.rc0-py38", + "stable-py311" : "2001.11-py311", + "testing-py311": "2001.11.rc0-py311"}}""", + "2001.12.rc0", + "deprecated", + "changed", + """{ + "links": { + "2001.11-py311": "2001.11.00-py311", + "2001.11-py38": "2001.11.00-py38", + "2001.12-py311": "2001.12.rc0-py311", + "2001.12-py38": "2001.12.rc0-py38", + "deprecated-py311": "2001.12-py311", + "deprecated-py38": "2001.12-py38", + "stable-py311": "2001.11-py311", + "stable-py38": "2001.11-py38", + "testing-py311": "2001.11.rc0-py311", + "testing-py38": "2001.11.rc0-py38" + } +} """, ), ], @@ -397,3 +457,75 @@ def test_multi_update(json_in, release_id, mode, changed, json_out): json_out, changed == "changed", ) + +@pytest.mark.parametrize( + ("json_in", "release_id", "mode", "changed", "suggested_root_links"), + # move deprecated, expect 2001.10-py27 added as root_link + [ + ( + """{"links": { + "2001.10-py27": "2001.10.03-py27", + "2001.11-py27": "2001.11.00-py27", + "2001.12-py27": "2001.12.rc0-py27", + "deprecated": "deprecated-py27", + "deprecated-py27": "2001.10.03-py27", + "stable": "stable-py27", + "stable-py27" : "2001.11-py27", + "testing": "testing-py27", + "testing-py27": "2001.12.rc0-py27" + }, + "root_links": [] + }""", + "2001.11.00-py27", + "deprecated", + "changed", + [ "testing", "deprecated", "stable", "2001.10-py27", "2001.12-py27" ], + ), + #move testing, expect 2001.12.rc0 but not 2002.01-py27 added as root_link + ( + """{"links": { + "2001.10-py27": "2001.10.03-py27", + "2001.11-py27": "2001.11.00-py27", + "2001.12-py27": "2001.12.rc0-py27", + "2002.01-py27": "2002.01.rc0-py27", + "deprecated": "deprecated-py27", + "deprecated-py27": "2001.10.03-py27", + "stable": "stable-py27", + "stable-py27" : "2001.11-py27", + "testing": "testing-py27", + "testing-py27": "2001.12.rc0-py27" + }, + "root_links": [] + }""", + "2002.01-py27", + "testing", + "changed", + [ "testing", "deprecated", "stable", "2001.10-py27", "2001.12-py27" ], + ), + # move stable, expect 2001.11-py27, but not 2001.12-py27 added as root_link + ( + """{"links": { + "2001.10-py27": "2001.10.03-py27", + "2001.11-py27": "2001.11.00-py27", + "2001.12-py27": "2001.12.rc0-py27", + "deprecated": "deprecated-py27", + "deprecated-py27": "2001.10.03-py27", + "stable": "stable-py27", + "stable-py27" : "2001.11-py27", + "testing": "testing-py27", + "testing-py27": "2001.12.rc0-py27" + }, + "root_links": [] + }""", + "2001.12-py27", + "stable", + "changed", + ["testing", "deprecated", "stable", "2001.10-py27", "2001.11-py27"], + ), + ], +) +def test_suggesting_dangling_root_links_update(json_in, release_id, mode, changed, suggested_root_links): + json_out, changed = update(json_in, release_id, mode) + assert changed + json_obj = json.loads(json_out) + assert json_obj["root_links"] == sorted(suggested_root_links)