#!/usr/bin/env python3 # SPDX-License-Identifier: Apache-2.0 # ----------------------------------------------------------------------------- # Copyright 2020-2021 Arm Limited # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy # of the License at: # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # ----------------------------------------------------------------------------- """ The functional test runner is a set of tests that validate the ``astcenc`` command line is correctly handled, under both valid and invalid usage scenarios. These tests do NOT validate the compression codec itself, beyond some very basic incidental usage needed to validate the command line. Due to the need to validate pixel colors in test images for both LDR and HDR images, these tests rely on an HDRI-enabled build of ImageMagic being available on the system path. To test if the version of ImageMagic on your system is HDRI-enabled run: convert --version ... and check that the string "HDRI" is present in the listed features. Test Tiles ========== Some basic test images, each 8x8 texels and built up from 4 no. 4x4 texel constant color blocks, are used to help determine that the command line is being processed correctly. LDR Test Pattern ---------------- LDR images are an 8x8 image containing 4 4x4 constant color blocks. Assuming (0, 0) is the top left (TL), the component uncompressed block colors are: * (0, 0) TL = Black, opaque = (0.00, 0.00, 0.00, 1.00) * (7, 0) TR = Red, opaque = (1.00, 0.00, 0.00, 1.00) * (0, 7) BL = White, opaque = (1.00, 1.00, 1.00, 1.00) * (7, 7) BR = Green, trans = (0.25, 0.75, 0.00, 0.87) HDR Test Pattern ---------------- HDR images are an 8x8 image containing 4 4x4 constant color blocks. Assuming (0, 0) is the top left (TL), the component uncompressed block colors are: * (0, 0) TL = LDR Black, opaque = (0.00, 0.00, 0.00, 1.00) * (7, 0) TR = HDR Red, opaque = (8.00, 0.00, 0.00, 1.00) * (0, 7) BL = HDR White, opaque = (3.98, 3.98, 3.98, 1.00) * (7, 7) BR = LDR Green, trans = (0.25, 0.75, 0.00, 0.87) """ import argparse import filecmp import os import re import signal import string import subprocess as sp import sys import tempfile import time import unittest import numpy from PIL import Image import testlib.encoder as te import testlib.image as tli # Enable these to always write out, irrespective of test result ASTCENC_CLI_ALWAYS = False ASTCENC_LOG_ALWAYS = False # Enable these to write out on failure for positive tests ASTCENC_CLI_ON_ERROR = True ASTCENC_LOG_ON_ERROR = True # Enable these to write out on failure for negative tests ASTCENC_CLI_ON_ERROR_NEG = True ASTCENC_LOG_ON_ERROR_NEG = True # LDR test pattern ASTCENC_TEST_PATTERN_LDR = { "TL": (0.00, 0.00, 0.00, 1.00), "TR": (1.00, 0.00, 0.00, 1.00), "BL": (1.00, 1.00, 1.00, 1.00), "BR": (0.25, 0.75, 0.00, 0.87) } # HDR test pattern ASTCENC_TEST_PATTERN_HDR = { "TL": (0.00, 0.00, 0.00, 1.00), "TR": (8.00, 0.00, 0.00, 1.00), "BL": (3.98, 3.98, 3.98, 1.00), "BR": (0.25, 0.75, 0.00, 0.87) } LDR_RGB_PSNR_PATTERN = re.compile(r"\s*PSNR \(LDR-RGB\): (.*) dB") g_TestEncoder = "avx2" class CLITestBase(unittest.TestCase): """ Command line interface base class. These tests are designed to test the command line is handled correctly. They are not detailed tests of the codec itself; only basic sanity checks that some type of processing occurred are used. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) encoder = te.Encoder2x(g_TestEncoder) self.binary = encoder.binary def setUp(self): """ Set up a test case. Create a new temporary directory for output files. """ self.tempDir = tempfile.TemporaryDirectory() def tearDown(self): """ Tear down a test case. Clean up the temporary directory created for output files. """ self.tempDir.cleanup() self.tempDir = None @staticmethod def get_ref_image_path(profile, mode, image): """ Get the path of a reference image on disk. Args: profile (str): The color profile. mode (str): The type of image to load. image (str): The image variant to load. Returns: str: The path to the test image file on disk. """ nameMux = { "LDR": { "input": "png", "comp": "astc" }, "LDRS": { "input": "png", "comp": "astc" }, "HDR": { "input": "exr", "comp": "astc" } } assert profile in nameMux.keys() assert mode in nameMux["LDR"].keys() scriptDir = os.path.dirname(__file__) fileName = "%s-%s-1x1.%s" % (profile, image, nameMux[profile][mode]) return os.path.join(scriptDir, "Data", fileName) def get_tmp_image_path(self, profile, mode): """ Get the path of a temporary output image on disk. Temporary files are automatically cleaned up when the test tearDown occurs. Args: profile (str): The color profile. "EXP" means explicit which means the "mode" parameter is interpreted as a literal file extension not a symbolic mode. mode (str): The type of image to load. Returns: str: The path to the test image file on disk. """ # Handle explicit mode if profile == "EXP": tmpFile, tmpPath = tempfile.mkstemp(mode, dir=self.tempDir.name) os.close(tmpFile) os.remove(tmpPath) return tmpPath # Handle symbolic modes nameMux = { "LDR": { "comp": ".astc", "decomp": ".png", "bad": ".foo" }, "LDRS": { "comp": ".astc", "decomp": ".png", "bad": ".foo" }, "HDR": { "comp": ".astc", "decomp": ".exr", "bad": ".foo" } } assert profile in nameMux.keys() assert mode in nameMux["LDR"].keys() suffix = nameMux[profile][mode] tmpFile, tmpPath = tempfile.mkstemp(suffix, dir=self.tempDir.name) os.close(tmpFile) os.remove(tmpPath) return tmpPath class CLIPTest(CLITestBase): """ Command line interface positive tests. These tests are designed to test the command line is handled correctly. They are not detailed tests of the codec itself; only basic sanity checks that some type of processing occurred are used. """ def compare(self, image1, image2): """ Utility function to compare two images. Note that this comparison tests only decoded color values; any file metadata is ignored, and encoding methods are not compared. Args: image1 (str): Path to the first image. image2 (str): Path to the second image. Returns: bool: ``True` if the images are the same, ``False`` otherwise. """ img1 = Image.open(image1) img2 = Image.open(image2) # Images must have same size if img1.size != img2.size: print("Size") return False # Images must have same number of color channels if img1.getbands() != img2.getbands(): # ... unless the only different is alpha self.assertEqual(img1.getbands(), ("R", "G", "B")) self.assertEqual(img2.getbands(), ("R", "G", "B", "A")) # ... and the alpha is always one bands = img2.split() alphaHist = bands[3].histogram() self.assertEqual(sum(alphaHist[:-1]), 0) # Generate a version of img2 without alpha img2 = Image.merge("RGB", (bands[0], bands[1], bands[2])) # Compute sum of absolute differences dat1 = numpy.array(img1) dat2 = numpy.array(img2) sad = numpy.sum(numpy.abs(dat1 - dat2)) if sad != 0: print(img1.load()[0, 0]) print(img2.load()[0, 0]) return sad == 0 def get_channel_rmse(self, image1, image2): """ Get the channel-by-channel root mean square error. Args: image1 (str): Path to the first image. image2 (str): Path to the second image. Returns: tuple: Tuple of floats containing the RMSE per channel. None: Images could not be compared because they are different size. """ img1 = Image.open(image1) img2 = Image.open(image2) # Images must have same size if img1.size != img2.size: return None # Images must have same number of color channels if img1.getbands() != img2.getbands(): # ... unless the only different is alpha self.assertEqual(img1.getbands(), ("R", "G", "B")) self.assertEqual(img2.getbands(), ("R", "G", "B", "A")) # ... and the alpha is always one bands = img2.split() alphaHist = bands[3].histogram() self.assertEqual(sum(alphaHist[:-1]), 0) # Generate a version of img2 without alpha img2 = Image.merge("RGB", (bands[0], bands[1], bands[2])) # Compute root mean square error img1bands = img1.split() img2bands = img2.split() rmseVals = [] imgBands = zip(img1bands, img2bands) for img1Ch, img2Ch in imgBands: imSz = numpy.prod(img1Ch.size) dat1 = numpy.array(img1Ch) dat2 = numpy.array(img2Ch) sad = numpy.sum(numpy.square(dat1 - dat2)) mse = numpy.divide(sad, imSz) rmse = numpy.sqrt(mse) rmseVals.append(rmse) return rmseVals @staticmethod def get_color_refs(mode, corners): """ Build a set of reference colors from apriori color list. Args: mode (str): The color mode (LDR, or HDR) corners (str or list): The corner or list of corners -- named TL, TR, BL, and BR -- to return. Returns: tuple: The color value, if corners was a name. [tuple]: List of color values, if corners was a list of names. """ modes = { "LDR": ASTCENC_TEST_PATTERN_LDR, "HDR": ASTCENC_TEST_PATTERN_HDR } if isinstance(corners, str): return [modes[mode][corners]] return [modes[mode][corner] for corner in corners] def assertColorSame(self, colorRef, colorNew, threshold=0.02, swiz=None): """ Test if a color is the similar to a reference. Will trigger a test failure if the colors are not within threshold. Args: colorRef (tuple): The reference color to compare with. colorNew (tuple): The new color. threshold (float): The allowed deviation from colorRef (ratio). swiz (str): The swizzle string (4 characters from the set `rgba01`), applied to the reference color. """ self.assertEqual(len(colorRef), len(colorNew)) # Swizzle the reference color if needed if swiz: self.assertEqual(len(swiz), len(colorRef)) remap = { "0": len(colorRef), "1": len(colorRef) + 1 } if len(colorRef) >= 1: remap["r"] = 0 if len(colorRef) >= 2: remap["g"] = 1 if len(colorRef) >= 3: remap["b"] = 2 if len(colorRef) >= 4: remap["a"] = 3 colorRefExt = list(colorRef) + [0.0, 1.0] colorRef = [colorRefExt[remap[s]] for s in swiz] for chRef, chNew in zip(colorRef, colorNew): deltaMax = chRef * threshold self.assertAlmostEqual(chRef, chNew, delta=deltaMax) def exec(self, command, pattern=None): """ Execute a positive test. Will trigger a test failure if the subprocess return code is any value other than zero. Args: command (list(str)): The command to execute. pattern (re.Pattern): The regex pattern to search for, must contain a single group (this is returned to the caller). The test will fail if no pattern match is found. Returns: str: The stdout output of the child process, or the first group from the passed regex pattern. """ try: result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE, universal_newlines=True, check=True) error = False except sp.CalledProcessError as ex: result = ex error = True # Emit debug logging if needed if ASTCENC_CLI_ALWAYS or (error and ASTCENC_CLI_ON_ERROR): # Format for shell replay print("\n" + " ".join(command)) # Format for script command list replay print("\n" + ", ".join(("\"%s\"" % x for x in command))) if ASTCENC_LOG_ALWAYS or (error and ASTCENC_LOG_ON_ERROR): print(result.stdout) rcode = result.returncode if rcode < 0: msg = "Exec died with signal %s" % signal.Signals(-rcode).name self.assertGreaterEqual(rcode, 0, msg) if rcode > 0: msg = "Exec died with application error %u" % rcode self.assertEqual(rcode, 0, msg) # If there is a regex pattern provided, then search for it if pattern: match = pattern.search(result.stdout) self.assertIsNotNone(match) return match.group(1) return result.stdout def test_ldr_compress(self): """ Test basic LDR compression. """ imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("LDR", "comp") imRef = self.get_ref_image_path("LDR", "comp", "A") command = [self.binary, "-cl", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(filecmp.cmp(imRef, imOut, False)) def test_srgb_compress(self): """ Test basic LDR sRGB compression. """ imIn = self.get_ref_image_path("LDRS", "input", "A") imOut = self.get_tmp_image_path("LDRS", "comp") imRef = self.get_ref_image_path("LDRS", "comp", "A") command = [self.binary, "-cs", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(filecmp.cmp(imRef, imOut, False)) def test_hdr_compress1(self): """ Test basic HDR + LDR alpha compression. """ imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("HDR", "comp") imRef = self.get_ref_image_path("HDR", "comp", "A") command = [self.binary, "-ch", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(filecmp.cmp(imRef, imOut, False)) def test_hdr_compress2(self): """ Test basic HDR + HDR alpha compression. """ imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("HDR", "comp") imRef = self.get_ref_image_path("HDR", "comp", "A") command = [self.binary, "-cH", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(filecmp.cmp(imRef, imOut, False)) def test_ldr_decompress(self): """ Test basic LDR decompression. """ imIn = self.get_ref_image_path("LDR", "comp", "A") imOut = self.get_tmp_image_path("LDR", "decomp") imRef = self.get_ref_image_path("LDR", "input", "A") command = [self.binary, "-dl", imIn, imOut] self.exec(command) self.assertTrue(self.compare(imRef, imOut)) def test_srgb_decompress(self): """ Test basic LDR sRGB decompression. """ imIn = self.get_ref_image_path("LDRS", "comp", "A") imOut = self.get_tmp_image_path("LDRS", "decomp") imRef = self.get_ref_image_path("LDRS", "input", "A") command = [self.binary, "-ds", imIn, imOut] self.exec(command) self.assertTrue(self.compare(imRef, imOut)) def test_hdr_decompress1(self): """ Test basic HDR + LDR alpha decompression. """ imIn = self.get_ref_image_path("HDR", "comp", "A") imOut = self.get_tmp_image_path("HDR", "decomp") imRef = self.get_ref_image_path("HDR", "input", "A") command = [self.binary, "-dh", imIn, imOut] self.exec(command) colRef = tli.Image(imRef).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colRef, colOut) def test_hdr_decompress2(self): """ Test basic HDR + HDR alpha decompression. """ imIn = self.get_ref_image_path("HDR", "comp", "A") imOut = self.get_tmp_image_path("HDR", "decomp") imRef = self.get_ref_image_path("HDR", "input", "A") command = [self.binary, "-dH", imIn, imOut] self.exec(command) colRef = tli.Image(imRef).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colRef, colOut) def test_ldr_roundtrip(self): """ Test basic LDR round-trip """ imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("LDR", "decomp") command = [self.binary, "-tl", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(self.compare(imIn, imOut)) def test_srgb_roundtrip(self): """ Test basic LDR sRGB round-trip """ imIn = self.get_ref_image_path("LDRS", "input", "A") imOut = self.get_tmp_image_path("LDRS", "decomp") command = [self.binary, "-ts", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) self.assertTrue(self.compare(imIn, imOut)) def test_hdr_roundtrip1(self): """ Test basic HDR + LDR alpha round-trip. """ imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("HDR", "decomp") command = [self.binary, "-th", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) colIn = tli.Image(imIn).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colIn, colOut) def test_hdr_roundtrip2(self): """ Test basic HDR + HDR alpha round-trip. """ imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("HDR", "decomp") command = [self.binary, "-tH", imIn, imOut, "6x6", "-exhaustive"] self.exec(command) colIn = tli.Image(imIn).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colIn, colOut) def test_valid_2d_block_sizes(self): """ Test all valid block sizes are accepted (2D images). """ blockSizes = [ "4x4", "5x4", "5x5", "6x5", "6x6", "8x5", "8x6", "10x5", "10x6", "8x8", "10x8", "10x10", "12x10", "12x12" ] imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("LDR", "decomp") for blk in blockSizes: with self.subTest(blockSize=blk): command = [self.binary, "-tl", imIn, imOut, blk, "-exhaustive"] self.exec(command) colIn = tli.Image(imIn).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colIn, colOut) def test_valid_3d_block_sizes(self): """ Test all valid block sizes are accepted (3D images). """ blockSizes = [ "3x3x3", "4x3x3", "4x4x3", "4x4x4", "5x4x4", "5x5x4", "5x5x5", "6x5x5", "6x6x5", "6x6x6" ] imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("LDR", "decomp") for blk in blockSizes: with self.subTest(blockSize=blk): command = [self.binary, "-tl", imIn, imOut, blk, "-exhaustive"] self.exec(command) colIn = tli.Image(imIn).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colIn, colOut) def test_valid_presets(self): """ Test all valid presets are accepted """ presets = ["-fastest", "-fast", "-medium", "-thorough", "-exhaustive"] imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("LDR", "decomp") for preset in presets: with self.subTest(preset=preset): command = [self.binary, "-tl", imIn, imOut, "4x4", preset] self.exec(command) colIn = tli.Image(imIn).get_colors((0, 0)) colOut = tli.Image(imOut).get_colors((0, 0)) self.assertColorSame(colIn, colOut) def test_valid_ldr_input_formats(self): """ Test valid LDR input file formats. """ imgFormats = ["bmp", "dds", "jpg", "ktx", "png", "tga"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = "./Test/Data/Tiles/ldr.%s" % imgFormat imOut = self.get_tmp_image_path("LDR", "decomp") command = [self.binary, "-tl", imIn, imOut, "4x4", "-fast"] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut).get_colors((7, 7)) self.assertColorSame(colIn, colOut) def test_valid_uncomp_ldr_output_formats(self): """ Test valid uncompressed LDR output file formats. """ imgFormats = ["bmp", "dds", "ktx", "png", "tga"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) command = [self.binary, "-tl", imIn, imOut, "4x4", "-fast"] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut).get_colors((7, 7)) self.assertColorSame(colIn, colOut) def test_valid_comp_ldr_output_formats(self): """ Test valid compressed LDR output file formats. """ imgFormats = ["astc", "ktx"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = self.get_ref_image_path("LDR", "input", "A") imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) imOut2 = self.get_tmp_image_path("LDR", "decomp") command = [self.binary, "-cl", imIn, imOut, "4x4", "-fast"] self.exec(command) command = [self.binary, "-dl", imOut, imOut2] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut2).get_colors((7, 7)) self.assertColorSame(colIn, colOut2) def test_valid_hdr_input_formats(self): """ Test valid HDR input file formats. """ imgFormats = ["exr", "hdr"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = "./Test/Data/Tiles/hdr.%s" % imgFormat imOut = self.get_tmp_image_path("HDR", "decomp") command = [self.binary, "-th", imIn, imOut, "4x4", "-fast"] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat, profile="hdr"): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut).get_colors((7, 7)) self.assertColorSame(colIn, colOut) def test_valid_uncomp_hdr_output_formats(self): """ Test valid uncompressed HDR output file formats. """ imgFormats = ["dds", "exr", "hdr", "ktx"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) command = [self.binary, "-th", imIn, imOut, "4x4", "-fast"] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat, profile="hdr"): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut).get_colors((7, 7)) self.assertColorSame(colIn, colOut) def test_valid_comp_hdr_output_formats(self): """ Test valid compressed HDR output file formats. """ imgFormats = ["astc", "ktx"] for imgFormat in imgFormats: with self.subTest(imgFormat=imgFormat): imIn = self.get_ref_image_path("HDR", "input", "A") imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) imOut2 = self.get_tmp_image_path("HDR", "decomp") command = [self.binary, "-ch", imIn, imOut, "4x4", "-fast"] self.exec(command) command = [self.binary, "-dh", imOut, imOut2] self.exec(command) # Check colors if image wrapper supports it if tli.Image.is_format_supported(imgFormat): colIn = tli.Image(imIn).get_colors((7, 7)) colOut = tli.Image(imOut2).get_colors((7, 7)) self.assertColorSame(colIn, colOut2) def test_compress_mask(self): """ Test compression of mask textures. """ decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Images/Small/LDR-RGB/ldr-rgb-10.png", decompFile, "4x4", "-exhaustive"] noMaskdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) command.append("-mask") maskdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) # Note that this test simply asserts that the "-mask" is connected and # affects the output. We don't test it does something useful; that it # outside the scope of this test case. self.assertNotEqual(noMaskdB, maskdB) def test_compress_normal_psnr(self): """ Test compression of normal textures using PSNR error metrics. """ decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Images/Small/LDR-XY/ldr-xy-00.png", decompFile, "5x5", "-exhaustive"] refdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) command.append("-normal") testdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) # Note that this test simply asserts that the "-normal_psnr" is # connected and affects the output. We don't test it does something # useful; that it outside the scope of this test case. self.assertNotEqual(refdB, testdB) def test_compress_normal_percep(self): """ Test compression of normal textures using perceptual error metrics. """ decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Images/Small/LDR-XY/ldr-xy-00.png", decompFile, "4x4", "-exhaustive"] refdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) command.append("-normal") command.append("-perceptual") testdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) # Note that this test simply asserts that the "-normal -percep" is # connected and affects the output. We don't test it does something # useful; that it outside the scope of this test case. self.assertNotEqual(refdB, testdB) def test_compress_esw(self): """ Test compression swizzles. """ # The swizzles to test swizzles = ["rgba", "g0r1", "rrrg"] # Compress a swizzled image for swizzle in swizzles: with self.subTest(swizzle=swizzle): decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Data/Tiles/ldr.png", decompFile, "4x4", "-exhaustive", "-esw", swizzle] self.exec(command) # Fetch the three color img = tli.Image(decompFile) colorVal = img.get_colors([(7, 7)]) colorRef = self.get_color_refs("LDR", "BR") self.assertColorSame(colorRef[0], colorVal[0], swiz=swizzle) def test_compress_dsw(self): """ Test decompression swizzles. """ # The swizzles to test swizzles = ["rgba", "g0r1", "rrrg"] # Decompress a swizzled image for swizzle in swizzles: with self.subTest(swizzle=swizzle): decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Data/Tiles/ldr.png", decompFile, "4x4", "-exhaustive", "-dsw", swizzle] self.exec(command) # Fetch the three color img = tli.Image(decompFile) colorVal = img.get_colors([(7, 7)]) colorRef = self.get_color_refs("LDR", "BR") self.assertColorSame(colorRef[0], colorVal[0], swiz=swizzle) def test_compress_esw_dsw(self): """ Test compression and decompression swizzles """ # Compress a swizzled image, and swizzle back in decompression decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Data/Tiles/ldr.png", decompFile, "4x4", "-exhaustive", "-esw", "gbar", "-dsw", "argb"] self.exec(command) # Fetch the three color img = tli.Image(decompFile) colorVal = img.get_colors([(7, 7)]) colorRef = self.get_color_refs("LDR", "BR") self.assertColorSame(colorRef[0], colorVal[0]) def test_compress_flip(self): """ Test LDR image flip on compression. """ # Compress a flipped image compFile = self.get_tmp_image_path("LDR", "comp") command = [ self.binary, "-cl", "./Test/Data/Tiles/ldr.png", compFile, "4x4", "-fast", "-yflip"] self.exec(command) # Decompress a non-flipped image decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-dl", compFile, decompFile] self.exec(command) # Compare TL (0, 0) with BL - should match colorRef = self.get_color_refs("LDR", "BL") img = tli.Image(decompFile) colorVal = img.get_colors([(0, 0)]) self.assertColorSame(colorRef[0], colorVal[0]) def test_decompress_flip(self): """ Test LDR image flip on decompression. """ # Compress a non-flipped image compFile = self.get_tmp_image_path("LDR", "comp") command = [ self.binary, "-cl", "./Test/Data/Tiles/ldr.png", compFile, "4x4", "-fast"] self.exec(command) # Decompress a flipped image decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-dl", compFile, decompFile, "-yflip"] self.exec(command) # Compare TL (0, 0) with BL - should match colorRef = self.get_color_refs("LDR", "BL") img = tli.Image(decompFile) colorVal = img.get_colors([(0, 0)]) self.assertColorSame(colorRef[0], colorVal[0]) def test_roundtrip_flip(self): """ Test LDR image flip on roundtrip (no flip should occur). """ # Compress and decompressed a flipped LDR image decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", "./Test/Data/Tiles/ldr.png", decompFile, "4x4", "-fast", "-yflip"] self.exec(command) # Compare TL (0, 0) with TL - should match - i.e. no flip colorRef = self.get_color_refs("LDR", "TL") img = tli.Image(decompFile) colorVal = img.get_colors([(0, 0)]) self.assertColorSame(colorRef[0], colorVal[0]) def test_channel_weighting(self): """ Test channel weighting. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) baseRMSE = self.get_channel_rmse(inputFile, decompFile) # Note: Using -cw can result in a worse result than not using -cw, # with regressions in RMSE for the high-weighted channel. This is # particularly an issue in synthetic images, as they are more likely to # hit corner cases in the heuristics. It happens to "work" for the # selected test image and these settings, but might start to fail in # future due to compressor changes. # Test each channel with a high weight for chIdx, chName in ((0, "R"), (1, "G"), (2, "B"), (3, "A")): with self.subTest(channel=chName): cwArg = ["%s" % (10 if x == chIdx else 1) for x in range(0, 4)] command2 = command + ["-cw"] + cwArg self.exec(command2) chRMSE = self.get_channel_rmse(inputFile, decompFile) self.assertLess(chRMSE[chIdx], baseRMSE[chIdx]) def test_partition_count_limit(self): """ Test partition count limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-partitioncountlimit", "1"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_partition_index_limit(self): """ Test partition index limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-partitionindexlimit", "1"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_blockmode_limit(self): """ Test block mode limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-blockmodelimit", "25"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_refinement_limit(self): """ Test refinement limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-refinementlimit", "1"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_candidate_limit(self): """ Test candidate limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-candidatelimit", "1"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_db_cutoff_limit(self): """ Test db cutoff limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-dblimit", "10"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce cutoff quality self.assertGreater(testRMSE, refRMSE) def test_2partition_early_limit(self): """ Test 2 partition early limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-2partitionlimitfactor", "1.0"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_3partition_early_limit(self): """ Test 3 partition early limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-3partitionlimitfactor", "1.0"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertNotEqual(testRMSE, refRMSE) def test_2plane_correlation_limit(self): """ Test 2 plane correlation limit. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-2planelimitcorrelation", "0.1"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should get worse (higher) if we reduce search space self.assertGreater(testRMSE, refRMSE) def test_deblock(self): """ Test deblock bias. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) command += ["-b", "1.8"] self.exec(command) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # RMSE should be different with deblocking self.assertNotEqual(testRMSE, refRMSE) def test_low_level_control_v(self): """ Test low level control options. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # Test that explicit defaults match same as implicit defaults command2 = command + ["-v", "0", "1", "1", "0", "0", "0"] self.exec(command2) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) self.assertEqual(testRMSE, refRMSE) # Mutate the values to check they are worse than ref subtests = [ ("ref", ["-v", "2.5", "0.85", "0.25", "0.75", "25.0", "0.03"]), ("radius", ["-v", "3.5", "0.85", "0.25", "0.75", "25.0", "0.03"]), ("power", ["-v", "2.5", "1.85", "0.25", "0.75", "25.0", "0.03"]), ("base", ["-v", "2.5", "0.85", "0.05", "0.75", "25.0", "0.03"]), ("avg", ["-v", "2.5", "0.85", "0.25", "2.75", "25.0", "0.03"]), ("stdev", ["-v", "2.5", "0.85", "0.25", "0.75", "99.0", "0.03"]), ("mix", ["-v", "2.5", "0.85", "0.25", "0.75", "25.0", "1.03"]) ] # Test that our mutations made it worse; note we manually chose values # that did for this test image - this is not guranteed for all images resultSet = set() for (name, params) in subtests: with self.subTest(param=name): testCommand = command + params self.exec(testCommand) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) self.assertGreater(testRMSE, refRMSE) resultSet.add(testRMSE) # Test that each mutation was "differently" worse; i.e. some new # error metric was used self.assertEqual(len(resultSet), len(subtests)) def test_low_level_control_v_issue_105(self): """ Test low level control options. Regression test for: * https://github.com/ARM-software/astc-encoder/issues/105 """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium", "-v", "0", "1", "2", "0", "0", "0"] # This should not segfault ... self.exec(command) def test_low_level_control_va(self): """ Test low level control options. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium", "-v", "2.5", "0.85", "0.25", "0.75", "25.0", "0.03"] self.exec(command) refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) # Test that explicit defaults match same as implicit defaults command2 = command + ["-va", "1", "1", "0", "0"] self.exec(command2) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) self.assertEqual(testRMSE, refRMSE) # Mutate the values to check they are worse than ref subtests = [ ("ref", ["-va", "2.8", "0.25", "0.75", "25.0"]), ("power", ["-va", "4.8", "0.25", "0.75", "25.0"]), ("base", ["-va", "2.8", "0.05", "0.75", "25.0"]), ("avg", ["-va", "2.8", "0.25", "2.75", "25.0"]), ("stdev", ["-va", "2.8", "0.25", "0.75", "99.0"]) ] # Test that our mutations made it worse; note we manually chose values # that did for this test image - this is not guranteed for all images resultSet = set() for (name, params) in subtests: with self.subTest(param=name): testCommand = command + params self.exec(testCommand) testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) self.assertNotEqual(testRMSE, refRMSE) resultSet.add(testRMSE) # Test that each mutation was "differently" worse; i.e. some new # error metric was used self.assertEqual(len(resultSet), len(subtests)) @unittest.skipIf(os.cpu_count() == 1, "Cannot test on single core host") def test_thread_count(self): """ Test codec thread count. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] start = time.time() self.exec(command) refTime = time.time() - start command += ["-j", "1"] start = time.time() self.exec(command) testTime = time.time() - start # Test time should get slower with fewer threads self.assertGreater(testTime, refTime) def test_silent(self): """ Test silent """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" decompFile = self.get_tmp_image_path("LDR", "decomp") # Compute the basic image without any channel weights command = [ self.binary, "-tl", inputFile, decompFile, "4x4", "-medium"] stdout = self.exec(command) command += ["-silent"] stdoutSilent = self.exec(command) # Check that stdout is shorter in silent mode. Note that this doesn't # check that it is as silent as it should be, just that silent is wired # somewhere ... self.assertLess(len(stdoutSilent), len(stdout)) def test_image_quality_stability(self): """ Test that a round-trip and a file-based round-trip give same result. """ inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" p1DecFile = self.get_tmp_image_path("LDR", "decomp") p2CompFile = self.get_tmp_image_path("LDR", "comp") p2DecFile = self.get_tmp_image_path("LDR", "decomp") # Compute the first image using a direct round-trip command = [self.binary, "-tl", inputFile, p1DecFile, "4x4", "-medium"] self.exec(command) # Compute the first image using a file-based round-trip command = [self.binary, "-cl", inputFile, p2CompFile, "4x4", "-medium"] self.exec(command) command = [self.binary, "-dl", p2CompFile, p2DecFile] self.exec(command) # RMSE should be the same p1RMSE = sum(self.get_channel_rmse(inputFile, p1DecFile)) p2RMSE = sum(self.get_channel_rmse(inputFile, p2DecFile)) self.assertEqual(p1RMSE, p2RMSE) class CLINTest(CLITestBase): """ Command line interface negative tests. These tests are designed to test that bad inputs to the command line are handled cleanly and that errors are correctly thrown. Note that many tests are mutations of a valid positive test command line, to ensure that the base command line is valid before it is mutated many of these tests include a *positive test* to ensure that the starting point is actually a valid command line (otherwise we could be throwing an arbitrary error). """ def exec(self, command, expectPass=False): """ Execute a negative test. Test will automatically fail if: * The subprocess return code is zero, unless ``expectPass==True``. * The subprocess correctly returned non-zero, but without any error message. * The subprocess dies with any kind of signal. Args: command (list(str)): The command to execute. expectPass (bool): ``True`` if this command is actually expected to pass, which is used to validate commands before mutating them. """ try: result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE, universal_newlines=True, check=True) error = False except sp.CalledProcessError as ex: # Pop out of the CPE scope to handle the error, as this reduces # test log verbosity on failure by avoiding nested exceptions result = ex error = True rcode = result.returncode # Emit debug logging if needed (negative rcode is a signal) badResult = (error == expectPass) or (rcode < 0) if ASTCENC_CLI_ALWAYS or (badResult and ASTCENC_CLI_ON_ERROR_NEG): # Format for shell replay print("\n" + " ".join(command)) # Format for script command list replay print("\n" + ", ".join(("\"%s\"" % x for x in command))) if ASTCENC_LOG_ALWAYS or (badResult and ASTCENC_LOG_ON_ERROR_NEG): print(result.stdout) # If we expected a pass, then rcode == 0 if expectPass: self.assertEqual(rcode, 0, "Exec did not pass as expected") self.assertNotIn("ERROR", result.stdout) return # If we got a negative that's always bad (signal of some kind) if rcode < 0: msg = "Exec died with signal %s" % signal.Signals(-rcode).name self.assertGreaterEqual(rcode, 0, msg) # Otherwise just assert that we got an error log, and some positive # return code value was returned self.assertIn("ERROR", result.stdout) self.assertGreater(rcode, 0, "Exec did not fail as expected") def exec_with_omit(self, command, startOmit): """ Execute a negative test with command line argument omission. These tests aim to prove that the command fails if arguments are missing. However the passed command MUST be a valid command which passes if no argument are omitted (this is checked, to ensure that the test case is a valid test). Test will automatically fail if: * A partial command doesn't fail. * The full command doesn't pass. """ # Run the command, incrementally omitting arguments commandLen = len(command) for subLen in range(startOmit, commandLen + 1): omit = len(command) - subLen with self.subTest(omit=omit): testCommand = command[:subLen] expectPass = omit == 0 self.exec(testCommand, expectPass) def test_cl_missing_args(self): """ Test -cl with missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast"] self.exec_with_omit(command, 2) def test_cl_missing_input(self): """ Test -cl with a missing input file. """ # Build a valid command with a missing input file command = [ self.binary, "-cl", "./Test/Data/missing.png", self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast"] self.exec(command) def test_cl_missing_input_array_slice(self): """ Test -cl with a missing input file in an array slice. """ # Build a valid command with a missing input file command = [ self.binary, "-cl", "./Test/Data/Tiles/ldr.png", self.get_tmp_image_path("LDR", "comp"), "3x3x3", "-fast", "-array", "3"] self.exec(command) def test_cl_unknown_input(self): """ Test -cl with an unknown input file extension. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", "./Test/Data/empty.unk", self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast"] self.exec(command) def test_cl_missing_output(self): """ Test -cl with a missing output directory. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), "./DoesNotExist/test.astc", "4x4", "-fast"] self.exec(command) def test_cl_unknown_output(self): """ Test -cl with an unknown output file extension. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), "./test.aastc", "4x4", "-fast"] self.exec(command) def test_cl_bad_block_size(self): """ Test -cl with an invalid block size. """ badSizes = [ "4x5", # Illegal 2D block size "3x3x4", # Illegal 3D block size "4x4x4x4", # Too many dimensions "4x", # Incomplete 2D block size "4x4x", # Incomplete 3D block size "4x4x4x", # Over-long 3D block size "4xe", # Illegal non-numeric character "4x4e" # Additional non-numeric character ] # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast"] # Test that the underlying command is valid self.exec(command, True) blockIndex = command.index("4x4") for badSize in badSizes: with self.subTest(blockSize=badSize): command[blockIndex] = badSize self.exec(command) def test_cl_bad_preset(self): """ Test -cl with an invalid encoding preset. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fastt"] self.exec(command) def test_cl_bad_argument(self): """ Test -cl with an unknown additional argument. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-unknown"] self.exec(command) def test_cl_2d_block_with_array(self): """ Test -cl with a 2D block size and 3D input data. """ # Build an otherwise valid command with the test flaw # TODO: This fails late (i.e. the data is still loaded, and we fail # at processing time when we see a 3D array). We could fail earlier at # parse time, which might consolidate the error handling code. command = [ self.binary, "-cl", "./Test/Data/Tiles/ldr.png", self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-array", "2"] self.exec(command) def test_cl_array_missing_args(self): """ Test -cl with a 2D block size and 3D input data. """ # Build an otherwise valid command command = [ self.binary, "-cl", "./Test/Data/Tiles/ldr.png", self.get_tmp_image_path("LDR", "comp"), "4x4x4", "-fast", "-array", "2"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_tl_missing_args(self): """ Test -tl with missing arguments. """ # Build a valid command command = [ self.binary, "-tl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fast"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 2) def test_tl_missing_input(self): """ Test -tl with a missing input file. """ # Build a valid command with a missing input file command = [ self.binary, "-tl", "./Test/Data/missing.png", self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fast"] self.exec(command) def test_tl_unknown_input(self): """ Test -tl with an unknown input file extension. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-tl", "./Test/Data/empty.unk", self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fast"] self.exec(command) def test_tl_missing_output(self): """ Test -tl with a missing output directory. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-tl", self.get_ref_image_path("LDR", "input", "A"), "./DoesNotExist/test.png", "4x4", "-fast"] self.exec(command) def test_tl_bad_block_size(self): """ Test -tl with an invalid block size. """ badSizes = [ "4x5", # Illegal 2D block size "3x3x4", # Illegal 3D block size "4x4x4x4", # Too many dimensions "4x", # Incomplete 2D block size "4x4x", # Incomplete 3D block size "4x4x4x", # Over-long 3D block size "4xe", # Illegal non-numeric character "4x4e" # Additional non-numeric character ] # Build an otherwise valid command with the test flaw command = [ self.binary, "-tl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fast"] # Test that the underlying command is valid self.exec(command, True) blockIndex = command.index("4x4") for badSize in badSizes: with self.subTest(blockSize=badSize): command[blockIndex] = badSize self.exec(command) def test_tl_bad_preset(self): """ Test -tl with an invalid encoding preset. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-tl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fastt"] self.exec(command) def test_tl_bad_argument(self): """ Test -tl with an unknown additional argument. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-tl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "decomp"), "4x4", "-fast", "-unknown"] self.exec(command) def test_dl_missing_args(self): """ Test -dl with missing arguments. """ # Build a valid command command = [ self.binary, "-dl", self.get_ref_image_path("LDR", "comp", "A"), self.get_tmp_image_path("LDR", "decomp")] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 2) def test_dl_missing_output(self): """ Test -dl with a missing output directory. """ # Build an otherwise valid command with the test flaw command = [ self.binary, "-dl", self.get_ref_image_path("LDR", "comp", "A"), "./DoesNotExist/test.png"] self.exec(command) def test_cl_v_missing_args(self): """ Test -cl with -v and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-v", "3", "1.1", "0.8", "0.25", "0.5", "0.04"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_va_missing_args(self): """ Test -cl with -va and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-va", "1.1", "0.8", "0.25", "0.5"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_a_missing_args(self): """ Test -cl with -a and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-a", "2"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_cw_missing_args(self): """ Test -cl with -cw and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-cw", "0", "1", "2", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_b_missing_args(self): """ Test -cl with -b and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-b", "1.6"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_partitionlimit_missing_args(self): """ Test -cl with -partitionindexlimit and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-partitionindexlimit", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_blockmodelimit_missing_args(self): """ Test -cl with -blockmodelimit and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-blockmodelimit", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_refinementlimit_missing_args(self): """ Test -cl with -refinementlimit and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-refinementlimit", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_dblimit_missing_args(self): """ Test -cl with -dblimit and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-dblimit", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_2partitionearlylimit_missing_args(self): """ Test -cl with -2partitionlimitfactor and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-2partitionlimitfactor", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_3partitionearlylimit_missing_args(self): """ Test -cl with -3partitionlimitfactor and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-3partitionlimitfactor", "3"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_2planeearlylimit_missing_args(self): """ Test -cl with -2planelimitcorrelation and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-2planelimitcorrelation", "0.66"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_esw_missing_args(self): """ Test -cl with -esw and missing arguments. """ # Build a valid command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-esw", "rgb1"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def test_cl_esw_invalid_swizzle(self): """ Test -cl with -esw and invalid swizzles. """ badSwizzles = [ "", # Short swizzles "r", "rr", "rrr", "rrrrr", # Long swizzles ] # Create swizzles with all invalid printable ascii codes good = ["r", "g", "b", "a", "0", "1"] for channel in string.printable: if channel not in good: badSwizzles.append(channel * 4) # Build a valid base command command = [ self.binary, "-cl", self.get_ref_image_path("LDR", "input", "A"), self.get_tmp_image_path("LDR", "comp"), "4x4", "-fast", "-esw", "rgba"] blockIndex = command.index("rgba") for badSwizzle in badSwizzles: with self.subTest(swizzle=badSwizzle): command[blockIndex] = badSwizzle self.exec(command) def test_dl_dsw_missing_args(self): """ Test -dl with -dsw and missing arguments. """ # Build a valid command command = [ self.binary, "-dl", self.get_ref_image_path("LDR", "comp", "A"), self.get_tmp_image_path("LDR", "decomp"), "-dsw", "rgb1"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 5) def test_dl_dsw_invalid_swizzle(self): """ Test -dl with -dsw and invalid swizzles. """ badSwizzles = [ "", # Short swizzles "r", "rr", "rrr", "rrrrr", # Long swizzles ] # Create swizzles with all invalid printable ascii codes good = ["r", "g", "b", "a", "z", "0", "1"] for channel in string.printable: if channel not in good: badSwizzles.append(channel * 4) # Build a valid base command command = [ self.binary, "-dl", self.get_ref_image_path("LDR", "comp", "A"), self.get_tmp_image_path("LDR", "decomp"), "-dsw", "rgba"] blockIndex = command.index("rgba") for badSwizzle in badSwizzles: with self.subTest(swizzle=badSwizzle): command[blockIndex] = badSwizzle self.exec(command) def test_ch_mpsnr_missing_args(self): """ Test -ch with -mpsnr and missing arguments. """ # Build a valid command command = [ self.binary, "-ch", self.get_ref_image_path("HDR", "input", "A"), self.get_tmp_image_path("HDR", "comp"), "4x4", "-fast", "-mpsnr", "-5", "5"] # Run the command, incrementally omitting arguments self.exec_with_omit(command, 7) def main(): """ The main function. Returns: int: The process return code. """ global g_TestEncoder parser = argparse.ArgumentParser() coders = ["none", "neon", "sse2", "sse4.1", "avx2"] parser.add_argument("--encoder", dest="encoder", default="avx2", choices=coders, help="test encoder variant") args = parser.parse_known_args() # Set the encoder for this test run g_TestEncoder = args[0].encoder # Set the sys.argv to remaining args (leave sys.argv[0] alone) sys.argv[1:] = args[1] results = unittest.main(exit=False) return 0 if results.result.wasSuccessful() else 1 if __name__ == "__main__": sys.exit(main())