1
0
Fork 0
mirror of https://github.com/matomo-org/matomo-icons.git synced 2024-09-18 15:53:44 +02:00
matomo-icons/tests.py

272 lines
9.8 KiB
Python
Raw Permalink Normal View History

#!/usr/bin/env python3
# Copyright (C) 2017 Lukas Winkler
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License as published by the Free Software
# Foundation, either version 3 of the License, or (at your option) any later
# version.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
2018-06-15 10:13:05 +02:00
import hashlib
2017-11-21 21:30:35 +01:00
import json
2018-06-15 10:13:05 +02:00
import os
import re
2022-02-19 21:14:08 +01:00
import subprocess
import sys
2022-02-19 21:14:08 +01:00
from pathlib import Path
from typing import Dict, List, Iterator, Iterable
2017-11-21 21:43:07 +01:00
from urllib.parse import urlparse
import yaml
from PIL import Image
build_script_regex = re.compile(r"rm [-rf]+ plugins/Morpheus/icons/(.*)")
2017-05-10 17:28:14 +02:00
min_image_size = 48
2017-05-08 20:48:32 +02:00
placeholder_icon_hash = "398a623a3b0b10eba6d1884b0ff1713ee12aeafaa8efaf67b60a4624f4dce48c"
2022-02-19 21:14:08 +01:00
searchEnginesFile = Path("vendor/matomo/searchengine-and-social-list/SearchEngines.yml")
socialsEnginesFile = Path("vendor/matomo/searchengine-and-social-list/Socials.yml")
build_script_file = Path("tmp/piwik-package/scripts/build-package.sh")
2022-02-19 21:14:08 +01:00
src = Path("src/")
dist: Path = Path("dist/")
2022-02-19 21:14:08 +01:00
def print_warning(string: str) -> None:
2017-11-23 19:15:13 +01:00
print("\033[33;1m⚠\033[0m " + string)
2017-11-22 08:27:49 +01:00
2022-02-19 21:14:08 +01:00
def print_error(string: str) -> None:
2017-11-23 19:15:13 +01:00
print("\033[31;1m⚠ " + string + "\033[0m")
2017-11-22 08:27:49 +01:00
2022-02-19 21:14:08 +01:00
def load_yaml(file: Path):
with file.open() as stream:
return yaml.safe_load(stream)
2022-02-19 21:14:08 +01:00
def image_exists(pathslug: Path) -> bool:
for filetype in ["svg", "png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
if pathslug.with_suffix(pathslug.suffix + f".{filetype}").exists():
return True
return False
2022-02-19 21:14:08 +01:00
def walk(path: Path) -> Iterator[Path]:
for p in path.iterdir():
if p.is_dir():
yield p
yield from walk(p)
continue
yield p.resolve()
def test_if_all_icons_are_converted(ignored_source_files) -> None:
global error
for filetype in ["svg", "png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
for file in src.glob(f"**/*.{filetype}"):
distfile = Path("dist/") / Path(*file.parts[1:]).with_suffix(".png")
2022-02-19 21:14:08 +01:00
if not distfile.exists() and file not in ignored_source_files:
print_error(f"{distfile} is missing (From {file})")
error = True
2022-02-19 21:14:08 +01:00
def test_if_source_for_images() -> None:
2017-05-07 17:30:53 +02:00
global error
for icontype in ["brand", "browsers", "os", "plugins", "SEO"]:
for filetype in ["svg", "png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
for source_file in (src / icontype).glob(f"*.{filetype}"):
if (
not source_file.is_symlink()
and not source_file.with_suffix(source_file.suffix + ".source")
and "UNK" not in source_file
):
print_error(f"Source is missing for {source_file}")
error = True
2017-05-07 17:30:53 +02:00
2022-02-19 21:14:08 +01:00
def test_if_all_symlinks_are_valid() -> None:
2017-05-08 17:13:14 +02:00
global error
2022-02-19 21:14:08 +01:00
for file in src.glob("**/*"):
if file.is_symlink():
target = file.resolve()
if not target.exists():
print_error(f"Symlink doesn't link to file (from {file} to {target} ({file.readlink()}))")
2022-02-19 22:14:10 +01:00
error = True
2017-05-08 17:13:14 +02:00
2022-02-19 21:14:08 +01:00
def test_if_placeholder_icon_exist(placeholder_icon_filenames: Dict[str, str]) -> None:
2017-05-08 20:48:32 +02:00
global error
for folder, filename in placeholder_icon_filenames.items():
2022-02-19 21:14:08 +01:00
file = src / folder / filename
if not (file.exists() and hashlib.sha256(file.read_bytes()).hexdigest() == placeholder_icon_hash):
print_error(f"The placeholder icon {file} is missing or invalid")
2017-05-08 20:48:32 +02:00
error = True
2022-02-19 21:14:08 +01:00
def test_if_icons_are_large_enough() -> None:
2017-05-10 17:28:14 +02:00
# ignore searchEngines and socials
for filetype in ["png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
for source_file in src.glob(f"*/*.{filetype}"):
im = Image.open(source_file)
if im.size[0] < min_image_size or im.size[1] < min_image_size:
2022-02-19 21:14:08 +01:00
width, height = im.size
2017-11-22 08:27:49 +01:00
print_warning(
2022-02-19 21:14:08 +01:00
f"{source_file} is smaller ({width}x{height}) that the target size ({min_image_size}x{min_image_size})"
)
if filetype in ["jpg", "gif", "ico"]:
2022-02-19 21:14:08 +01:00
print_warning(
f"{source_file} is saved in a lossy image format ({filetype}). "
"Maybe try to find an PNG or SVG from another source."
)
2017-05-10 17:28:14 +02:00
2022-02-19 21:14:08 +01:00
def test_if_dist_icons_are_square(ignore_that_icon_isnt_square: List[str]) -> None:
global error
2022-02-19 21:14:08 +01:00
for file in dist.glob("**/*.png"):
if (dist / "flags") not in file.parents:
im = Image.open(file)
2022-02-19 21:14:08 +01:00
width, height = im.size
if width != height:
string = f"{file} isn't square ({width}x{height})"
2022-02-19 22:14:10 +01:00
if str(file) not in ignore_that_icon_isnt_square:
2017-05-18 19:01:48 +02:00
error = True
2017-11-22 08:27:49 +01:00
print_error(string)
else:
print_warning(string)
2017-05-18 19:01:48 +02:00
2022-02-19 21:14:08 +01:00
def is_in_allowed_dir(file: Path) -> bool:
allowed_dirs = [dist, Path("node_modules"), Path("tmp"), Path("vendor"), Path(".idea")]
for dir in allowed_dirs:
dir = dir.resolve()
if dir == file:
return True
if dir in file.parents:
return True
return False
def is_deleted(file: Path, deleted_files: Iterable[Path]) -> bool:
# print(file, deleted_files)
for del_file in deleted_files:
if del_file == file:
return True
if del_file in file.parents:
return True
return False
def test_if_build_script_is_deleting_all_unneeded_files() -> None:
global error
2022-02-19 21:14:08 +01:00
build_script = build_script_file.read_text()
deleted_files = set()
all_files = set(walk(Path(".").resolve()))
for pattern in build_script_regex.findall(build_script):
2022-02-19 21:14:08 +01:00
deleted_files.update(Path(".").resolve().glob(pattern))
for file in all_files:
2022-02-19 21:14:08 +01:00
if is_deleted(file, deleted_files):
continue
if is_in_allowed_dir(file):
continue
if file == Path("README.md").resolve():
continue
print_error(f"{file} should be deleted by the build script")
error = True
2022-02-19 21:14:08 +01:00
def test_if_icons_are_indicated_to_be_improvable() -> None:
for file in src.glob("**/*.todo"):
print_warning(f"{str(file)[:-5]} could be improved")
2022-02-19 21:14:08 +01:00
def look_for_search_and_social_icon(source, mode, outputdir: Path) -> None:
2017-11-16 22:20:01 +01:00
global error
2022-02-19 21:14:08 +01:00
correct_files = set()
for i, element in source.items():
if mode == "searchengines":
search_engine = element[0]
urls = search_engine["urls"]
else:
urls = element
2017-11-21 21:30:35 +01:00
url = next((url for url in urls if "{}" not in url), False)
url = urlparse("https://" + url).netloc
2022-02-19 21:14:08 +01:00
if url and not image_exists(outputdir / url):
print_error(f"icon for {url} is missing")
2017-11-16 22:20:01 +01:00
error = True
2022-02-19 21:14:08 +01:00
correct_files.add(url)
2017-11-16 22:20:01 +01:00
for filetype in ["svg", "png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
for file in outputdir.glob(f"*.{filetype}"):
domain = file.stem
2017-11-17 12:53:43 +01:00
if domain not in correct_files and domain != "xx":
2022-02-19 21:14:08 +01:00
print_error(f"{file} is not necessary")
2017-11-17 12:53:43 +01:00
error = True
2022-02-19 21:14:08 +01:00
def test_if_all_search_and_social_sites_have_an_icon() -> None:
look_for_search_and_social_icon(load_yaml(searchEnginesFile), "searchengines", Path("src/searchEngines/"))
look_for_search_and_social_icon(load_yaml(socialsEnginesFile), "socials", Path("src/socials/"))
2017-11-21 21:43:07 +01:00
2022-02-19 21:14:08 +01:00
def test_if_there_are_icons_for_all_device_detector_categories(
less_important_device_detector_icons: Dict[str, List[str]]
) -> None:
global error
2022-02-19 21:14:08 +01:00
output = subprocess.run(["php", "devicedetector.php"], capture_output=True)
regex = re.compile(r"[^a-z0-9_\-]+", re.IGNORECASE)
categories = json.loads(output.stdout)
2019-07-03 11:07:31 +02:00
for icontype, category in categories.items():
2017-11-21 21:32:36 +01:00
for code in category:
2019-07-03 11:07:31 +02:00
if icontype == "brand":
2019-08-13 16:46:49 +02:00
slug = regex.sub("_", category[code])
2017-11-21 21:32:36 +01:00
else:
slug = code
found = False
for filetype in ["svg", "png", "gif", "jpg", "ico"]:
2022-02-19 21:14:08 +01:00
file = Path(f"src/{icontype}/{slug}.{filetype}")
if file.exists():
2017-11-21 21:32:36 +01:00
found = True
2022-02-19 21:14:08 +01:00
break
2017-11-21 21:32:36 +01:00
if not found:
2022-02-19 21:14:08 +01:00
warning = f"icon for {category[code]} missing (should be at src/{icontype}/{slug}.{{png|svg}})"
2019-07-03 11:07:31 +02:00
if slug in less_important_device_detector_icons[icontype]:
print_warning(warning)
else:
print_error(warning)
2022-02-19 22:14:10 +01:00
# error = True
2017-11-21 21:32:36 +01:00
2017-05-10 17:28:14 +02:00
if __name__ == "__main__":
error = False
2022-02-19 21:14:08 +01:00
ignore = load_yaml(Path("tests-ignore.yml"))
2017-05-10 17:28:14 +02:00
if "TRAVIS_PULL_REQUEST" not in os.environ or not os.environ["TRAVIS_PULL_REQUEST"]:
test_if_all_icons_are_converted(ignore["ignored_source_files"])
2017-05-07 17:30:53 +02:00
test_if_source_for_images()
2017-05-08 17:13:14 +02:00
test_if_all_symlinks_are_valid()
test_if_placeholder_icon_exist(ignore["placeholder_icon_filenames"])
test_if_dist_icons_are_square(ignore["ignore_that_icon_isnt_square"])
2022-02-19 21:14:08 +01:00
travis = "TRAVIS" in os.environ and os.environ["TRAVIS"] # collapse on travis
if travis:
print("travis_fold:start:improvable_icons")
2017-05-11 08:31:38 +02:00
print("improvable icons: (click to expand)")
2022-02-19 21:14:08 +01:00
test_if_there_are_icons_for_all_device_detector_categories(ignore["less_important_device_detector_icons"])
test_if_icons_are_indicated_to_be_improvable()
test_if_icons_are_large_enough()
if travis:
print("travis_fold:end:improvable_icons")
2022-02-19 21:14:08 +01:00
test_if_all_search_and_social_sites_have_an_icon()
2023-07-22 21:58:38 +02:00
# test_if_build_script_is_deleting_all_unneeded_files()
sys.exit(error)