lbry-android-sdk/p4a/tests/test_graph.py
2022-11-29 15:35:24 -05:00

236 lines
8.7 KiB
Python

from pythonforandroid.build import Context
from pythonforandroid.graph import (
fix_deplist, get_dependency_tuple_list_for_recipe,
get_recipe_order_and_bootstrap, obvious_conflict_checker,
)
from pythonforandroid.bootstrap import Bootstrap
from pythonforandroid.recipe import Recipe
from pythonforandroid.util import BuildInterruptingException
from itertools import product
from unittest import mock
import pytest
ctx = Context()
name_sets = [['python3'],
['kivy']]
bootstraps = [None,
Bootstrap.get_bootstrap('sdl2', ctx)]
valid_combinations = list(product(name_sets, bootstraps))
valid_combinations.extend(
[(['python3'], Bootstrap.get_bootstrap('sdl2', ctx)),
(['kivy', 'python3'], Bootstrap.get_bootstrap('sdl2', ctx)),
(['flask'], Bootstrap.get_bootstrap('webview', ctx)),
(['pysdl2'], None), # auto-detect bootstrap! important corner case
]
)
invalid_combinations = [
[['pil', 'pillow'], None],
[['pysdl2', 'genericndkbuild'], None],
]
invalid_combinations_simple = list(invalid_combinations)
# NOTE !! keep in mind when setting invalid_combinations_simple:
#
# This is used to test obvious_conflict_checker(), which only
# catches CERTAIN conflicts:
#
# This must be a list of conflicts where the conflict is ONLY in
# non-tuple/non-ambiguous dependencies, e.g.:
#
# dependencies_1st = ["python2", "pillow"]
# dependencies_2nd = ["python3", "pillow"]
#
# This however won't work:
#
# dependencies_1st = [("python2", "python3"), "pillow"]
#
# (This is simply because the conflict checker doesn't resolve this to
# keep the code ismple enough)
def get_fake_recipe(name, depends=None, conflicts=None):
recipe = mock.Mock()
recipe.name = name
recipe.get_opt_depends_in_list = lambda: []
recipe.get_dir_name = lambda: name
recipe.depends = list(depends or [])
recipe.conflicts = list(conflicts or [])
return recipe
def register_fake_recipes_for_test(monkeypatch, recipe_list):
_orig_get_recipe = Recipe.get_recipe
def mock_get_recipe(name, ctx):
for recipe in recipe_list:
if recipe.name == name:
return recipe
return _orig_get_recipe(name, ctx)
# Note: staticmethod() needed for python ONLY, don't ask me why:
monkeypatch.setattr(Recipe, 'get_recipe', staticmethod(mock_get_recipe))
@pytest.mark.parametrize('names,bootstrap', valid_combinations)
def test_valid_recipe_order_and_bootstrap(names, bootstrap):
get_recipe_order_and_bootstrap(ctx, names, bootstrap)
@pytest.mark.parametrize('names,bootstrap', invalid_combinations)
def test_invalid_recipe_order_and_bootstrap(names, bootstrap):
with pytest.raises(BuildInterruptingException) as e_info:
get_recipe_order_and_bootstrap(ctx, names, bootstrap)
assert "conflict" in e_info.value.message.lower()
def test_blacklist():
# First, get order without blacklist:
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ["python3", "kivy"], None
)
# Now, obtain again with blacklist:
build_order_2, python_modules_2, bs_2 = get_recipe_order_and_bootstrap(
ctx, ["python3", "kivy"], None, blacklist=["libffi"]
)
assert "libffi" not in build_order_2
assert set(build_order_2).union({"libffi"}) == set(build_order)
# Check that we get a conflict when using webview and kivy combined:
wbootstrap = Bootstrap.get_bootstrap('webview', ctx)
with pytest.raises(BuildInterruptingException) as e_info:
get_recipe_order_and_bootstrap(ctx, ["flask", "kivy"], wbootstrap)
assert "conflict" in e_info.value.message.lower()
# We should no longer get a conflict blacklisting sdl2:
get_recipe_order_and_bootstrap(
ctx, ["flask", "kivy"], wbootstrap, blacklist=["sdl2"]
)
def test_get_dependency_tuple_list_for_recipe(monkeypatch):
r = get_fake_recipe("recipe1", depends=[
"libffi",
("libffi", "Pillow")
])
dep_list = get_dependency_tuple_list_for_recipe(
r, blacklist={"libffi"}
)
assert dep_list == [("pillow",)]
@pytest.mark.parametrize('names,bootstrap', valid_combinations)
def test_valid_obvious_conflict_checker(names, bootstrap):
# Note: obvious_conflict_checker is stricter on input
# (needs fix_deplist) than get_recipe_order_and_bootstrap!
obvious_conflict_checker(ctx, fix_deplist(names))
@pytest.mark.parametrize('names,bootstrap',
invalid_combinations_simple # see above for why this
) # is a separate list
def test_invalid_obvious_conflict_checker(names, bootstrap):
# Note: obvious_conflict_checker is stricter on input
# (needs fix_deplist) than get_recipe_order_and_bootstrap!
with pytest.raises(BuildInterruptingException) as e_info:
obvious_conflict_checker(ctx, fix_deplist(names))
assert "conflict" in e_info.value.message.lower()
def test_misc_obvious_conflict_checker(monkeypatch):
# Check that the assert about wrong input data is hit:
with pytest.raises(AssertionError) as e_info:
obvious_conflict_checker(
ctx,
["this_is_invalid"]
# (invalid because it isn't properly nested as tuple)
)
# Test that non-recipe dependencies work in overall:
obvious_conflict_checker(
ctx, fix_deplist(["python3", "notarecipelibrary"])
)
# Test that a conflict with a non-recipe dependency works:
# This is currently not used, so we need a custom test recipe:
# To get that, we simply modify one!
with monkeypatch.context() as m:
register_fake_recipes_for_test(m, [
get_fake_recipe("recipe1", conflicts=[("fakelib")]),
])
with pytest.raises(BuildInterruptingException) as e_info:
obvious_conflict_checker(ctx, fix_deplist(["recipe1", "fakelib"]))
assert "conflict" in e_info.value.message.lower()
# Test a case where a recipe pulls in a conditional tuple
# of additional dependencies. This is e.g. done for ('python3',
# 'python2', ...) but most recipes don't depend on this anymore,
# so we need to add a manual test for this case:
with monkeypatch.context() as m:
register_fake_recipes_for_test(m, [
get_fake_recipe("recipe1", depends=[("libffi", "Pillow")]),
])
obvious_conflict_checker(ctx, fix_deplist(["recipe1"]))
def test_indirectconflict_obvious_conflict_checker(monkeypatch):
# Test a case where there's an indirect conflict, which also
# makes sure the error message correctly blames the OUTER recipes
# as original conflict source:
with monkeypatch.context() as m:
register_fake_recipes_for_test(m, [
get_fake_recipe("outerrecipe1", depends=["innerrecipe1"]),
get_fake_recipe("outerrecipe2", depends=["innerrecipe2"]),
get_fake_recipe("innerrecipe1"),
get_fake_recipe("innerrecipe2", conflicts=["innerrecipe1"]),
])
with pytest.raises(BuildInterruptingException) as e_info:
obvious_conflict_checker(
ctx,
fix_deplist(["outerrecipe1", "outerrecipe2"])
)
assert ("conflict" in e_info.value.message.lower() and
"outerrecipe1" in e_info.value.message.lower() and
"outerrecipe2" in e_info.value.message.lower())
def test_multichoice_obvious_conflict_checker(monkeypatch):
# Test a case where there's a conflict with a multi-choice tuple:
with monkeypatch.context() as m:
register_fake_recipes_for_test(m, [
get_fake_recipe("recipe1", conflicts=["lib1", "lib2"]),
get_fake_recipe("recipe2", depends=[("lib1", "lib2")]),
])
with pytest.raises(BuildInterruptingException) as e_info:
obvious_conflict_checker(
ctx,
fix_deplist([("lib1", "lib2"), "recipe1"])
)
assert "conflict" in e_info.value.message.lower()
def test_bootstrap_dependency_addition():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ['kivy'], None)
assert ('hostpython3' in build_order)
def test_graph_deplist_transformation():
test_pairs = [
(["Pillow", ('python2', 'python3')],
[('pillow',), ('python2', 'python3')]),
(["Pillow", ('python2',)],
[('pillow',), ('python2',)]),
]
for (before_list, after_list) in test_pairs:
assert fix_deplist(before_list) == after_list
def test_bootstrap_dependency_addition2():
build_order, python_modules, bs = get_recipe_order_and_bootstrap(
ctx, ['kivy', 'python3'], None)
assert 'hostpython3' in build_order
if __name__ == "__main__":
get_recipe_order_and_bootstrap(ctx, ['python3'],
Bootstrap.get_bootstrap('sdl2', ctx))