From e940b604f3a2a0c47dba35a1527d57b96008ee65 Mon Sep 17 00:00:00 2001 From: poikilos <7557867+poikilos@users.noreply.github.com> Date: Sun, 7 Apr 2024 18:49:22 -0400 Subject: [PATCH] Add a test for image loading (related to issue #14). --- .../channeltinkerpil/test_channeltinkerpil.py | 128 ++++++++++++++++++ tests/data/license.txt | 4 + .../pil-compatible/farming_orange-resaved.png | Bin 0 -> 678 bytes .../pil-incompatible/coderskins_not_avail.png | Bin 0 -> 6757 bytes .../data/pil-incompatible/farming_orange.png | Bin 0 -> 6771 bytes 5 files changed, 132 insertions(+) create mode 100644 tests/data/license.txt create mode 100644 tests/data/pil-compatible/farming_orange-resaved.png create mode 100644 tests/data/pil-incompatible/coderskins_not_avail.png create mode 100644 tests/data/pil-incompatible/farming_orange.png diff --git a/tests/channeltinkerpil/test_channeltinkerpil.py b/tests/channeltinkerpil/test_channeltinkerpil.py index f55ce2c..0a7c905 100644 --- a/tests/channeltinkerpil/test_channeltinkerpil.py +++ b/tests/channeltinkerpil/test_channeltinkerpil.py @@ -2,10 +2,78 @@ import os import platform +import sys +import unittest from unittest import TestCase +TESTS_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +TEST_DATA_DIR = os.path.join(TESTS_DIR, "data") + +if __name__ == "__main__": + # ^ dirname twice since nested (tests/*/*.py) + REPO_DIR = os.path.dirname(TESTS_DIR) + sys.path.insert(0, REPO_DIR) + print("[test_channeltinkerpil] using {}".format(REPO_DIR)) +else: + print("__name__={}".format(__name__)) + +# from channeltinker import diff_images + +from channeltinkerpil import diff_images_by_path from channeltinkerpil.diffimage import diff_image_files_and_gen +from rotocanvas import sysdirs + +diff_base = os.path.join( + sysdirs['HOME'], + "Nextcloud/www.etc/minetest.org/www/imgsite/backgrounds/bg_hard_rock.png" +) + +# test_cmd_parts = [ +# "findbyappearance", +# diff_base, +# os.path.join(sysdirs['HOME'], "minetest/games/bucket_game"), +# ] + +# pil_incompatible_dir = os.path.join(sysdirs['HOME'], "minetest", "games", +# "bucket_game") + +pil_incompatible_dir = os.path.join(TEST_DATA_DIR, "pil-incompatible") +pil_incompatible_files = [ + # 'mods/codercore/bucket/projects/textures000/bucket_lava_crust.png', # corrupt, ignore # noqa: E501 + # 'mods/codercore/coderskins/textures/coderskins_not_avail.png', + # 'mods/codercore/default/textures/default_ladder_wood.png', + # 'mods/codercore/default/textures/default_lava_crust.png', + # 'mods/codercore/default/textures/default_lava_crust_flowing_animated.png', + # 'mods/codercore/default/textures/default_lava_crust_source_animated.png', + # 'mods/coderfood/farming/textures/farming_orange.png', + "coderskins_not_avail.png", + "farming_orange.png", + # 'mods/codermobs/codermobs/projects/textures000/codermobs_bom_mesh.png', + # 'mods/codermobs/codermobs/projects/textures000/codermobs_chicken_egg.png', + # 'mods/codermobs/codermobs/projects/textures000/codermobs_denny_mesh.png', + # 'mods/codermobs/codermobs/projects/textures000/codermobs_hen_mesh.png', + # 'mods/codermobs/codermobs/projects/textures000/codermobs_mdskeleton_mesh.png', # noqa E501 + # 'mods/codermobs/codermobs/textures/codermobs_denny_mesh.png', + # 'mods/codermobs/codermobs/textures/codermobs_mdskeleton_mesh.png', + # 'mods/codermobs/mobs/projects/textures000/mobs_chicken_egg.png', + # 'mods/codermobs/mobs/projects/textures000/mobs_chicken_egg_overlay.png', +] +pil_compatible_dir = pil_incompatible_dir +pil_compatible_files = [ + 'mods/coderbuild/xdecor/projects/textures000/ench_ui.png', + 'mods/codercore/bones/projects/textures000/bones_bottom.png', + 'mods/codercore/bones/projects/textures000/bones_rear.png', + 'mods/codercore/bones/projects/textures000/bones_top.png', + 'mods/codercore/default/textures/default_chest_front.jpg', + 'mods/codercore/prestibags/textures/prestibags_red.jpg', + 'mods/codercore/default/textures/default_chest_lock.jpg', + 'mods/codermobs/petores/projects/textures000/ironstone.png', + 'mods/codermobs/codermobs/projects/textures000/codermobs_goat_brown.png', + 'mods/codercore/default/textures/default_chest_top.jpg', +] + + class TestChanneltinkerpil(TestCase): def test_diffimagewriting(self): tempDir = "/tmp" @@ -26,3 +94,63 @@ def test_diffimagewriting(self): print("* removed: {}".format(tempPngPath)) print("All tests passed.") + + def test_pil_compatible_png(self): + """Test PIL-incompatible PNG files. + (See issue #14) + Whatever method that passes this test should be used by *all*: + - findbyappearance + - diffimage + - diffimage-gui + - imagepx + """ + found_compatible = 0 + found_incompatible = 0 + for sub in pil_incompatible_files: + sub = sub.replace("/", os.path.sep) + sub_path = os.path.join(pil_incompatible_dir, sub) + if not os.path.isfile(sub_path): + print('Warning: no "{}"'.format(sub_path)) + continue + + diff = diff_images_by_path(diff_base, sub_path) + if diff.get('error'): + raise Exception("{}:".format(sub_path)+diff['error']) + if diff['head'].get('error'): + raise Exception("{}:".format(sub_path)+diff['head']['error']) + if diff['base'].get('error'): + raise Exception("{}:".format(diff_base)+diff['base']['error']) + if 'same' not in diff: + raise KeyError("Missing 'same' in {}".format(diff)) + assert diff['same'] is False + found_incompatible += 1 + + for sub in pil_compatible_files: + sub = sub.replace("/", os.path.sep) + sub_path = os.path.join(pil_compatible_dir, sub) + + if not os.path.isfile(sub_path): + print('Warning: no "{}"'.format(sub_path)) + continue + + diff = diff_images_by_path(diff_base, sub_path) + if diff.get('error'): + raise Exception("{}:".format(sub_path)+diff['error']) + if diff['head'].get('error'): + raise Exception("{}:".format(sub_path)+diff['head']['error']) + if diff['base'].get('error'): + raise Exception("{}:".format(diff_base)+diff['base']['error']) + if 'same' not in diff: + raise KeyError("Missing 'same' in {}".format(diff)) + assert diff['same'] is False + + found_compatible += 1 + + if found_incompatible < 1: + raise FileNotFoundError( + "Can't do test since no test files are present." + ) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file diff --git a/tests/data/license.txt b/tests/data/license.txt new file mode 100644 index 0000000..eb1e085 --- /dev/null +++ b/tests/data/license.txt @@ -0,0 +1,4 @@ + +Created by NyankoSensei, VanessaE, Poikilos (CC0 formerly WTFPL): + farming_orange.png + - hmm, the upstream one can be opened by PIL, but Poikilo's (saved by GIMP) cannot. What's up with that? diff --git a/tests/data/pil-compatible/farming_orange-resaved.png b/tests/data/pil-compatible/farming_orange-resaved.png new file mode 100644 index 0000000000000000000000000000000000000000..515f5cd403561eede9ea9ef83c9ea29ac5295ce6 GIT binary patch literal 678 zcmV;X0$KfuP)VD*kg zVk(gTnyjzu9zTEekXbc0T1j;0(ldKQ(0HJYl?79W@7ek#nNiTfeWGV&up?dH+|e#Mko|(M;>2ae6b-Y+;vP!427VVi}^uZCrwY< z*0U1}v99NoW!GY28+Mm{PVCZr3xHZV0z``-u?FP=7L13lv5ck^4KWQ^qDW{1&RK9F z>k5s?ux}zS09NK`Y|Mbs$(I?D6<96`AK(A&ncbY38a1|~s8ySXt7U)j>8+*Uj|@It zOPg$zA|F`HnJmriLsV*Q!Z0C@5-cT|`;#vlKlB=)o>L-%YZat{=@5h=BM5uL77y$F z%vB9(ouY3`8qaI_-HQdln^)Q@NGC`e(itJ>7<5~RJ%b$!Ymp`qMQIHhO~F|kWdD_S z>nQ?YNTL>`VgWUVV+%>2A+=MGZGzhq(?di?NA$WOah&{|fj6&I{6Qk8AJ}?|kQS+E z0h&M)iY-N_1EnI2P6qPZ5bHT50#<2JM{Wq^CKP=*c_a7_zx@lo1LsNdcXNkWt^fc4 M07*qoM6N<$f)`>RbpQYW literal 0 HcmV?d00001 diff --git a/tests/data/pil-incompatible/coderskins_not_avail.png b/tests/data/pil-incompatible/coderskins_not_avail.png new file mode 100644 index 0000000000000000000000000000000000000000..a9307aa21bda69be3945501966ea3fde20359d24 GIT binary patch literal 6757 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 q