Skip to content

Commit

Permalink
split out test_checks_universal.py
Browse files Browse the repository at this point in the history
  • Loading branch information
felipesanches committed Nov 12, 2024
1 parent 9797f3c commit e422c14
Show file tree
Hide file tree
Showing 14 changed files with 633 additions and 710 deletions.
38 changes: 38 additions & 0 deletions tests/test_checks_case_mapping.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from fontTools.ttLib import TTFont

from conftest import check_id
from fontbakery.status import FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)
from fontbakery.utils import remove_cmap_entry


@check_id("case_mapping")
def test_check_case_mapping(check):
"""Ensure the font supports case swapping for all its glyphs."""

ttFont = TTFont(TEST_FILE("merriweather/Merriweather-Regular.ttf"))
# Glyph present in the font Missing case-swapping counterpart
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# U+01D3: LATIN CAPITAL LETTER U WITH CARON U+01D4: LATIN SMALL LETTER U WITH CARON
# U+01E6: LATIN CAPITAL LETTER G WITH CARON U+01E7: LATIN SMALL LETTER G WITH CARON
# U+01F4: LATIN CAPITAL LETTER G WITH ACUTE U+01F5: LATIN SMALL LETTER G WITH ACUTE
assert_results_contain(check(ttFont), FAIL, "missing-case-counterparts")

# While we'd expect designers to draw the missing counterparts,
# for testing purposes we can simply delete the glyphs that lack a counterpart
# to make the check PASS:
remove_cmap_entry(ttFont, 0x01D3)
remove_cmap_entry(ttFont, 0x01E6)
remove_cmap_entry(ttFont, 0x01F4)
assert_PASS(check(ttFont))

# Let's add something which *does* have case swapping but which isn't a letter
# to ensure the check doesn't fail for such glyphs.
for table in ttFont["cmap"].tables:
table.cmap[0x2160] = "uni2160" # ROMAN NUMERAL ONE, which downcases to 0x2170
assert 0x2170 not in ttFont.getBestCmap()
assert_PASS(check(ttFont))
122 changes: 122 additions & 0 deletions tests/test_checks_fontbakery_version.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
from unittest.mock import patch, MagicMock

import pytest
import requests

from conftest import check_id
from fontbakery.status import FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)
from fontbakery.checks.fontbakery import is_up_to_date


@pytest.mark.parametrize(
"installed, latest, result",
[
# True when installed >= latest
("0.5.0", "0.5.0", True),
("0.5.1", "0.5.0", True),
("0.5.1", "0.5.0.post2", True),
("2.0.0", "1.5.1", True),
("0.8.10", "0.8.9", True),
("0.5.2.dev73+g8c9ebc0.d20181023", "0.5.1", True),
("0.8.10.dev1+g666b3425", "0.8.9", True),
("0.8.10.dev2+gfa9260bf", "0.8.9.post2", True),
("0.8.10a9", "0.8.9", True),
("0.8.10rc1.dev3+g494879af.d20220825", "0.8.9", True),
# False when installed < latest
("0.4.1", "0.5.0", False),
("0.3.4", "0.3.5", False),
("1.0.0", "1.0.1", False),
("0.8.9", "0.8.10", False),
("0.5.0", "0.5.0.post2", False),
("0.8.9.dev1+g666b3425", "0.8.9.post2", False),
("0.5.2.dev73+g8c9ebc0.d20181023", "0.5.2", False),
("0.5.2.dev73+g8c9ebc0.d20181023", "0.5.3", False),
("0.8.10rc0", "0.8.10", False),
("0.8.10rc0", "0.8.10.post", False),
("0.8.10rc1.dev3+g494879af.d20220825", "0.8.10", False),
("0.8.10rc1.dev3+g494879af.d20220825", "0.8.10.post", False),
],
)
def test_is_up_to_date(installed, latest, result):
assert is_up_to_date(installed, latest) is result


class MockDistribution:
"""Helper class to mock pip-api's Distribution class."""

def __init__(self, version: str):
self.name = "fontbakery"
self.version = version

def __repr__(self):
return f"<Distribution(name='{self.name}', version='{self.version}')>"


# We don't want to make an actual GET request to PyPI.org, so we'll mock it.
# We'll also mock pip-api's 'installed_distributions' method.
@patch("pip_api.installed_distributions")
@patch("requests.get")
def test_check_fontbakery_version(mock_get, mock_installed):
"""Check if FontBakery is up-to-date"""
from fontbakery.codetesting import CheckTester

check = CheckTester("fontbakery_version")

# Any of the test fonts can be used here.
# The check requires a 'font' argument but it doesn't do anything with it.
font = TEST_FILE("nunito/Nunito-Regular.ttf")

mock_response = MagicMock()
mock_response.status_code = 200

# Test the case of installed version being the same as PyPI's version.
latest_ver = installed_ver = "0.1.0"
mock_response.json.return_value = {"info": {"version": latest_ver}}
mock_get.return_value = mock_response
mock_installed.return_value = {"fontbakery": MockDistribution(installed_ver)}
assert_PASS(check(font))

# Test the case of installed version being newer than PyPI's version.
installed_ver = "0.1.1"
mock_installed.return_value = {"fontbakery": MockDistribution(installed_ver)}
assert_PASS(check(font))

# Test the case of installed version being older than PyPI's version.
installed_ver = "0.0.1"
mock_installed.return_value = {"fontbakery": MockDistribution(installed_ver)}
msg = assert_results_contain(check(font), FAIL, "outdated-fontbakery")
assert (
f"Current FontBakery version is {installed_ver},"
f" while a newer {latest_ver} is already available."
) in msg

# Test the case of an unsuccessful response to the GET request.
mock_response.status_code = 500
mock_response.content = "500 Internal Server Error"
msg = assert_results_contain(check(font), FAIL, "unsuccessful-request-500")
assert "Request to PyPI.org was not successful" in msg

# Test the case of the GET request failing due to a connection error.
mock_get.side_effect = requests.exceptions.ConnectionError
msg = assert_results_contain(check(font), FAIL, "connection-error")
assert "Request to PyPI.org failed with this message" in msg


@pytest.mark.xfail(reason="Often happens until rebasing")
@check_id("fontbakery_version")
def test_check_fontbakery_version_live_apis(check):
"""Check if FontBakery is up-to-date. (No API-mocking edition)"""

# Any of the test fonts can be used here.
# The check requires a 'font' argument but it doesn't do anything with it.
font = TEST_FILE("nunito/Nunito-Regular.ttf")

# The check will make an actual request to PyPI.org,
# and will query 'pip' to determine which version of 'fontbakery' is installed.
# The check should PASS.
assert_PASS(check(font))
53 changes: 53 additions & 0 deletions tests/test_checks_mandatory_glyphs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from fontTools.ttLib import TTFont

from conftest import check_id
from fontbakery.status import WARN, FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)


@check_id("mandatory_glyphs")
def test_check_mandatory_glyphs(check):
"""Font contains the first few mandatory glyphs (.null or NULL, CR and space)?"""
from fontTools import subset

ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf"))
assert_PASS(check(ttFont))

options = subset.Options()
options.glyph_names = True # Preserve glyph names
# By default, the subsetter keeps the '.notdef' glyph but removes its outlines
subsetter = subset.Subsetter(options)
subsetter.populate(text="mn") # Arbitrarily remove everything except 'm' and 'n'
subsetter.subset(ttFont)
message = assert_results_contain(check(ttFont), FAIL, "notdef-is-blank")
assert message == "The '.notdef' glyph should contain a drawing, but it is blank."

options.notdef_glyph = False # Drop '.notdef' glyph
subsetter = subset.Subsetter(options)
subsetter.populate(text="mn")
subsetter.subset(ttFont)
message = assert_results_contain(check(ttFont), WARN, "notdef-not-found")
assert message == "Font should contain the '.notdef' glyph."

# Change the glyph name from 'n' to '.notdef'
# (Must reload the font here since we already decompiled the glyf table)
ttFont = TTFont(TEST_FILE("nunito/Nunito-Regular.ttf"))
ttFont.glyphOrder = ["m", ".notdef"]
for subtable in ttFont["cmap"].tables:
if subtable.isUnicode():
subtable.cmap[110] = ".notdef"
if 0 in subtable.cmap:
del subtable.cmap[0]
results = check(ttFont)
message = assert_results_contain([results[0]], WARN, "notdef-not-first")
assert message == "The '.notdef' should be the font's first glyph."

message = assert_results_contain([results[1]], WARN, "notdef-has-codepoint")
assert message == (
"The '.notdef' glyph should not have a Unicode codepoint value assigned,"
" but has 0x006E."
)
32 changes: 32 additions & 0 deletions tests/test_checks_name_trailing_spaces.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from fontTools.ttLib import TTFont

from conftest import check_id
from fontbakery.status import FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)


@check_id("name/trailing_spaces")
def test_check_name_trailing_spaces(check):
"""Name table entries must not have trailing spaces."""

# Our reference Cabin Regular is known to be good:
ttFont = TTFont(TEST_FILE("cabin/Cabin-Regular.ttf"))
assert_PASS(check(ttFont), "with a good font...")

for i, entry in enumerate(ttFont["name"].names):
good_string = ttFont["name"].names[i].toUnicode()
bad_string = good_string + " "
ttFont["name"].names[i].string = bad_string.encode(entry.getEncoding())
assert_results_contain(
check(ttFont),
FAIL,
"trailing-space",
f'with a bad name table entry ({i}: "{bad_string}")...',
)

# restore good entry before moving to the next one:
ttFont["name"].names[i].string = good_string.encode(entry.getEncoding())
27 changes: 27 additions & 0 deletions tests/test_checks_ots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from conftest import check_id
from fontbakery.status import WARN, FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)


@check_id("ots")
def test_check_ots(check):
"""Checking with ots-sanitize."""

fine_font = TEST_FILE("cabin/Cabin-Regular.ttf")
assert_PASS(check(fine_font))

warn_font = TEST_FILE("bad_fonts/ots/bad_post_version.otf")
message = assert_results_contain(check(warn_font), WARN, "ots-sanitize-warn")
assert (
"WARNING: post: Only version supported for fonts with CFF table is"
" 0x00030000 not 0x20000" in message
)

bad_font = TEST_FILE("bad_fonts/ots/no_glyph_data.ttf")
message = assert_results_contain(check(bad_font), FAIL, "ots-sanitize-error")
assert "ERROR: no supported glyph data table(s) present" in message
assert "Failed to sanitize file!" in message
31 changes: 31 additions & 0 deletions tests/test_checks_rupee.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from fontTools.ttLib import TTFont

from conftest import check_id
from fontbakery.status import SKIP, FAIL
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)


@check_id("rupee")
def test_check_rupee(check):
"""Ensure indic fonts have the Indian Rupee Sign glyph."""

ttFont = TTFont(TEST_FILE("mada/Mada-Regular.ttf"))
msg = assert_results_contain(check(ttFont), SKIP, "unfulfilled-conditions")
assert "Unfulfilled Conditions: is_indic_font" in msg

# This one is good:
ttFont = TTFont(
TEST_FILE("indic-font-with-rupee-sign/NotoSerifDevanagari-Regular.ttf")
)
assert_PASS(check(ttFont))

# But this one lacks the glyph:
ttFont = TTFont(
TEST_FILE("indic-font-without-rupee-sign/NotoSansOlChiki-Regular.ttf")
)
msg = assert_results_contain(check(ttFont), FAIL, "missing-rupee")
assert msg == "Please add a glyph for Indian Rupee Sign (₹) at codepoint U+20B9."
47 changes: 47 additions & 0 deletions tests/test_checks_soft_hyphen.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from fontTools.ttLib import TTFont
import pytest

from conftest import check_id
from fontbakery.status import WARN
from fontbakery.codetesting import (
assert_PASS,
assert_results_contain,
TEST_FILE,
)
from fontbakery.utils import remove_cmap_entry


@pytest.fixture
def montserrat_ttFonts():
paths = [
TEST_FILE("montserrat/Montserrat-Black.ttf"),
TEST_FILE("montserrat/Montserrat-BlackItalic.ttf"),
TEST_FILE("montserrat/Montserrat-Bold.ttf"),
TEST_FILE("montserrat/Montserrat-BoldItalic.ttf"),
TEST_FILE("montserrat/Montserrat-ExtraBold.ttf"),
TEST_FILE("montserrat/Montserrat-ExtraBoldItalic.ttf"),
TEST_FILE("montserrat/Montserrat-ExtraLight.ttf"),
TEST_FILE("montserrat/Montserrat-ExtraLightItalic.ttf"),
TEST_FILE("montserrat/Montserrat-Italic.ttf"),
TEST_FILE("montserrat/Montserrat-Light.ttf"),
TEST_FILE("montserrat/Montserrat-LightItalic.ttf"),
TEST_FILE("montserrat/Montserrat-Medium.ttf"),
TEST_FILE("montserrat/Montserrat-MediumItalic.ttf"),
TEST_FILE("montserrat/Montserrat-Regular.ttf"),
TEST_FILE("montserrat/Montserrat-SemiBold.ttf"),
TEST_FILE("montserrat/Montserrat-SemiBoldItalic.ttf"),
TEST_FILE("montserrat/Montserrat-Thin.ttf"),
TEST_FILE("montserrat/Montserrat-ThinItalic.ttf"),
]
return [TTFont(path) for path in paths]


@check_id("soft_hyphen")
def test_check_soft_hyphen(check, montserrat_ttFonts):
"""Check glyphs contain the recommended contour count"""
for ttFont in montserrat_ttFonts:
# Montserrat has a softhyphen...
assert_results_contain(check(ttFont), WARN, "softhyphen")

remove_cmap_entry(ttFont, 0x00AD)
assert_PASS(check(ttFont))
22 changes: 22 additions & 0 deletions tests/test_checks_ttx_roundtrip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from conftest import check_id

# from fontbakery.status import FAIL
from fontbakery.codetesting import (
assert_PASS,
# assert_results_contain,
TEST_FILE,
)


@check_id("ttx_roundtrip")
def test_check_ttx_roundtrip(check):
"""Checking with fontTools.ttx"""

font = TEST_FILE("mada/Mada-Regular.ttf")
assert_PASS(check(font))

# TODO: Can anyone show us a font file that fails ttx roundtripping?!
#
# font = TEST_FILE("...")
# assert_results_contain(check(font),
# FAIL, None) # FIXME: This needs a message keyword
Loading

0 comments on commit e422c14

Please sign in to comment.