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

Validate (at least some) args #251

Merged
merged 17 commits into from
Oct 10, 2023
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
6 changes: 6 additions & 0 deletions qgispluginci/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ def cli():
release_parser.add_argument(
"release_version", help="The version to be released (x.y.z)."
)
release_parser.add_argument(
"--no-validation",
action="store_true",
help="Turn off validation of `release version`",
)
release_parser.add_argument(
"--release-tag",
help="The release tag, if different from the version (e.g. vx.y.z).",
Expand Down Expand Up @@ -152,6 +157,7 @@ def cli():
push_tr_parser.add_argument("transifex_token", help="The Transifex API token")

args = parser.parse_args()
Parameters.validate_args(args)

# set log level depending on verbosity argument
args.verbosity = 40 - (10 * args.verbosity) if args.verbosity > 0 else 0
Expand Down
57 changes: 55 additions & 2 deletions qgispluginci/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@
# ########## Libraries #############
# ##################################

# standard library
import configparser
import datetime
import logging
import os
import re
import sys

# standard library
from argparse import Namespace
from pathlib import Path
from typing import Any, Callable, Dict, Iterator, Literal, Optional, Tuple
from typing import Any, Callable, Dict, Iterator, Optional, Tuple

import toml
import yaml
Expand Down Expand Up @@ -228,6 +231,56 @@ def __init__(self, definition: Dict[str, Any]):
)
self.repository_url = get_metadata("repository")

@staticmethod
def get_release_version_patterns() -> Dict[str, re.Pattern]:
return {
"simple": r"\d+\.\d+$",
"double": r"\d+\.\d+\.\d+$",
"v2": r"^v\d+\.\d+$",
"v3": r"^v\d+\.\d+\.\d+$",
# See https://github.com/semver/semver/blob/master/semver.md#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
"semver": r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$",
}

@staticmethod
def validate_args(args: Namespace):
"""
Raise an exception just in case:
- the user didn't opt-out of validation using the `--no-validation` flag; and
- the value of `release_version` matches no supported pattern.
In any case, warn the user if the value of `release_version` doesn't match the semver pattern.
"""
if not args.release_version:
return

patterns = Parameters.get_release_version_patterns()
semver_compliance = re.match(patterns.pop("semver"), args.release_version)

if not semver_compliance:
logging.warning(
f"Be aware that '{args.release_version}' is not a semver-compliant version."
)

if args.no_validation:
logging.warning("Disabled release version validation.")
return

if semver_compliance or any(
re.match(other_pattern, args.release_version)
for other_pattern in patterns.values()
):
return

raise ValueError(
f"""
Unable to validate the release version '{args.release_version}'.
Please use a release version identifier such as '1.0.1' (recommended, semantic versioning), 'v1.1.1', 'v1.1', or '1.1'.
Otherwise you can disable validation by running this command again with an extra '--no-validation' flag.
Semantic versioning (semvar) identifiers are recommended.
Take a look at https://en.wikipedia.org/wiki/Software_versioning#Semantic_versioning for a refresher."
"""
)

@staticmethod
def archive_name(
plugin_name, release_version: str, experimental: bool = False
Expand Down
41 changes: 41 additions & 0 deletions test/test_release.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#! /usr/bin/env python

# standard
import argparse
import filecmp
import os
import re
import unittest
import urllib.request
from itertools import product
from pathlib import Path
from tempfile import mkstemp
from zipfile import ZipFile
Expand Down Expand Up @@ -205,6 +207,45 @@ def test_release_changelog(self):
# Commit sha1 not in the metadata.txt
self.assertEqual(0, len(re.findall(r"commitSha1=\d+", str(data))))

def test_release_version_valid_invalid(self):
valid_tags = ["v1.1.1", "v1.1", "1.0.1", "1.1", "1.0.0-alpha", "1.0.0-dev"]
invalid_tags = ["1", "v1", ".", ".1"]
expected_valid_results = {
"v1.1.1": ["v3"],
"v1.1": ["v2"],
"1.0.1": ["double", "semver"],
"1.1": ["simple"],
"1.0.0-alpha": ["semver"],
"1.0.0-dev": ["semver"],
}
valid_results = {tag: [] for tag in valid_tags}
patterns = Parameters.get_release_version_patterns()
for key, cand in product(patterns, valid_results):
if re.match(patterns[key], cand):
valid_results[cand].append(key)
self.assertEqual(valid_results, expected_valid_results)

invalid_results = {tag: [] for tag in invalid_tags}
for key, cand in product(patterns, invalid_results):
if re.match(patterns[key], cand):
invalid_results[cand].append(key)
self.assertFalse(any(invalid_results.values()))

def test_release_version_validation_on(self):
parser = argparse.ArgumentParser()
parser.add_argument("release_version")
parser.add_argument("--no-validation", action="store_true")
args = parser.parse_args(["v1"])
with self.assertRaises(ValueError):
Parameters.validate_args(args)

def test_release_version_validation_off(self):
parser = argparse.ArgumentParser()
parser.add_argument("release_version")
parser.add_argument("--no-validation", action="store_true")
args = parser.parse_args([".", "--no-validation"])
Parameters.validate_args(args)


if __name__ == "__main__":
unittest.main()