1#!/usr/bin/env python3 2# SPDX-License-Identifier: Apache-2.0 3# ----------------------------------------------------------------------------- 4# Copyright 2020-2023 Arm Limited 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); you may not 7# use this file except in compliance with the License. You may obtain a copy 8# of the License at: 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 14# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 15# License for the specific language governing permissions and limitations 16# under the License. 17# ----------------------------------------------------------------------------- 18""" 19The functional test runner is a set of tests that validate the ``astcenc`` 20command line is correctly handled, under both valid and invalid usage 21scenarios. These tests do NOT validate the compression codec itself, beyond 22some very basic incidental usage needed to validate the command line. 23 24Due to the need to validate pixel colors in test images for both LDR and HDR 25images, these tests rely on an HDRI-enabled build of ImageMagic being available 26on the system path. To test if the version of ImageMagic on your system is 27HDRI-enabled run: 28 29 convert --version 30 31... and check that the string "HDRI" is present in the listed features. 32 33Test Tiles 34========== 35 36Some basic test images, each 8x8 texels and built up from 4 no. 4x4 texel 37constant color blocks, are used to help determine that the command line is 38being processed correctly. 39 40LDR Test Pattern 41---------------- 42 43LDR images are an 8x8 image containing 4 4x4 constant color blocks. Assuming 44(0, 0) is the top left (TL), the component uncompressed block colors are: 45 46* (0, 0) TL = Black, opaque = (0.00, 0.00, 0.00, 1.00) 47* (7, 0) TR = Red, opaque = (1.00, 0.00, 0.00, 1.00) 48* (0, 7) BL = White, opaque = (1.00, 1.00, 1.00, 1.00) 49* (7, 7) BR = Green, trans = (0.25, 0.75, 0.00, 0.87) 50 51HDR Test Pattern 52---------------- 53 54HDR images are an 8x8 image containing 4 4x4 constant color blocks. Assuming 55(0, 0) is the top left (TL), the component uncompressed block colors are: 56 57* (0, 0) TL = LDR Black, opaque = (0.00, 0.00, 0.00, 1.00) 58* (7, 0) TR = HDR Red, opaque = (8.00, 0.00, 0.00, 1.00) 59* (0, 7) BL = HDR White, opaque = (3.98, 3.98, 3.98, 1.00) 60* (7, 7) BR = LDR Green, trans = (0.25, 0.75, 0.00, 0.87) 61""" 62 63import argparse 64import filecmp 65import os 66import re 67import signal 68import string 69import subprocess as sp 70import sys 71import tempfile 72import time 73import unittest 74 75import numpy 76from PIL import Image 77 78import testlib.encoder as te 79import testlib.image as tli 80 81# Enable these to always write out, irrespective of test result 82ASTCENC_CLI_ALWAYS = False 83ASTCENC_LOG_ALWAYS = False 84 85# Enable these to write out on failure for positive tests 86ASTCENC_CLI_ON_ERROR = True 87ASTCENC_LOG_ON_ERROR = True 88 89# Enable these to write out on failure for negative tests 90ASTCENC_CLI_ON_ERROR_NEG = True 91ASTCENC_LOG_ON_ERROR_NEG = True 92 93# LDR test pattern 94ASTCENC_TEST_PATTERN_LDR = { 95 "TL": (0.00, 0.00, 0.00, 1.00), 96 "TR": (1.00, 0.00, 0.00, 1.00), 97 "BL": (1.00, 1.00, 1.00, 1.00), 98 "BR": (0.25, 0.75, 0.00, 0.87) 99} 100 101# HDR test pattern 102ASTCENC_TEST_PATTERN_HDR = { 103 "TL": (0.00, 0.00, 0.00, 1.00), 104 "TR": (8.00, 0.00, 0.00, 1.00), 105 "BL": (3.98, 3.98, 3.98, 1.00), 106 "BR": (0.25, 0.75, 0.00, 0.87) 107} 108 109LDR_RGB_PSNR_PATTERN = re.compile(r"\s*PSNR \(LDR-RGB\): (.*) dB") 110 111g_TestEncoder = "avx2" 112 113class CLITestBase(unittest.TestCase): 114 """ 115 Command line interface base class. 116 117 These tests are designed to test the command line is handled correctly. 118 They are not detailed tests of the codec itself; only basic sanity checks 119 that some type of processing occurred are used. 120 """ 121 122 def __init__(self, *args, **kwargs): 123 super().__init__(*args, **kwargs) 124 125 encoder = te.Encoder2x(g_TestEncoder) 126 self.binary = encoder.binary 127 128 def setUp(self): 129 """ 130 Set up a test case. 131 132 Create a new temporary directory for output files. 133 """ 134 self.tempDir = tempfile.TemporaryDirectory() 135 136 def tearDown(self): 137 """ 138 Tear down a test case. 139 140 Clean up the temporary directory created for output files. 141 """ 142 self.tempDir.cleanup() 143 self.tempDir = None 144 145 @staticmethod 146 def get_ref_image_path(profile, mode, image): 147 """ 148 Get the path of a reference image on disk. 149 150 Args: 151 profile (str): The color profile. 152 mode (str): The type of image to load. 153 image (str): The image variant to load. 154 155 Returns: 156 str: The path to the test image file on disk. 157 """ 158 nameMux = { 159 "LDR": { 160 "input": "png", 161 "comp": "astc" 162 }, 163 "LDRS": { 164 "input": "png", 165 "comp": "astc" 166 }, 167 "HDR": { 168 "input": "exr", 169 "comp": "astc" 170 } 171 } 172 173 assert profile in nameMux.keys() 174 assert mode in nameMux["LDR"].keys() 175 176 scriptDir = os.path.dirname(__file__) 177 fileName = "%s-%s-1x1.%s" % (profile, image, nameMux[profile][mode]) 178 return os.path.join(scriptDir, "Data", fileName) 179 180 def get_tmp_image_path(self, profile, mode): 181 """ 182 Get the path of a temporary output image on disk. 183 184 Temporary files are automatically cleaned up when the test tearDown 185 occurs. 186 187 Args: 188 profile (str): The color profile. "EXP" means explicit which means 189 the "mode" parameter is interpreted as a literal file 190 extension not a symbolic mode. 191 mode (str): The type of image to load. 192 193 Returns: 194 str: The path to the test image file on disk. 195 """ 196 # Handle explicit mode 197 if profile == "EXP": 198 tmpFile, tmpPath = tempfile.mkstemp(mode, dir=self.tempDir.name) 199 os.close(tmpFile) 200 os.remove(tmpPath) 201 return tmpPath 202 203 # Handle symbolic modes 204 nameMux = { 205 "LDR": { 206 "comp": ".astc", 207 "decomp": ".png", 208 "bad": ".foo" 209 }, 210 "LDRS": { 211 "comp": ".astc", 212 "decomp": ".png", 213 "bad": ".foo" 214 }, 215 "HDR": { 216 "comp": ".astc", 217 "decomp": ".exr", 218 "bad": ".foo" 219 } 220 } 221 222 assert profile in nameMux.keys() 223 assert mode in nameMux["LDR"].keys() 224 225 suffix = nameMux[profile][mode] 226 tmpFile, tmpPath = tempfile.mkstemp(suffix, dir=self.tempDir.name) 227 os.close(tmpFile) 228 os.remove(tmpPath) 229 return tmpPath 230 231 232class CLIPTest(CLITestBase): 233 """ 234 Command line interface positive tests. 235 236 These tests are designed to test the command line is handled correctly. 237 They are not detailed tests of the codec itself; only basic sanity checks 238 that some type of processing occurred are used. 239 """ 240 241 def compare(self, image1, image2): 242 """ 243 Utility function to compare two images. 244 245 Note that this comparison tests only decoded color values; any file 246 metadata is ignored, and encoding methods are not compared. 247 248 Args: 249 image1 (str): Path to the first image. 250 image2 (str): Path to the second image. 251 252 Returns: 253 bool: ``True` if the images are the same, ``False`` otherwise. 254 """ 255 img1 = Image.open(image1) 256 img2 = Image.open(image2) 257 258 # Images must have same size 259 if img1.size != img2.size: 260 print("Size") 261 return False 262 263 # Images must have same number of color channels 264 if img1.getbands() != img2.getbands(): 265 # ... unless the only different is alpha 266 self.assertEqual(img1.getbands(), ("R", "G", "B")) 267 self.assertEqual(img2.getbands(), ("R", "G", "B", "A")) 268 269 # ... and the alpha is always one 270 bands = img2.split() 271 alphaHist = bands[3].histogram() 272 self.assertEqual(sum(alphaHist[:-1]), 0) 273 274 # Generate a version of img2 without alpha 275 img2 = Image.merge("RGB", (bands[0], bands[1], bands[2])) 276 277 # Compute sum of absolute differences 278 dat1 = numpy.array(img1) 279 dat2 = numpy.array(img2) 280 sad = numpy.sum(numpy.abs(dat1 - dat2)) 281 282 if sad != 0: 283 print(img1.load()[0, 0]) 284 print(img2.load()[0, 0]) 285 286 return sad == 0 287 288 def get_channel_rmse(self, image1, image2): 289 """ 290 Get the channel-by-channel root mean square error. 291 292 Args: 293 image1 (str): Path to the first image. 294 image2 (str): Path to the second image. 295 296 Returns: 297 tuple: Tuple of floats containing the RMSE per channel. 298 None: Images could not be compared because they are different size. 299 """ 300 img1 = Image.open(image1) 301 img2 = Image.open(image2) 302 303 # Images must have same size 304 if img1.size != img2.size: 305 return None 306 307 # Images must have same number of color channels 308 if img1.getbands() != img2.getbands(): 309 # ... unless the only different is alpha 310 self.assertEqual(img1.getbands(), ("R", "G", "B")) 311 self.assertEqual(img2.getbands(), ("R", "G", "B", "A")) 312 313 # ... and the alpha is always one 314 bands = img2.split() 315 alphaHist = bands[3].histogram() 316 self.assertEqual(sum(alphaHist[:-1]), 0) 317 318 # Generate a version of img2 without alpha 319 img2 = Image.merge("RGB", (bands[0], bands[1], bands[2])) 320 321 # Compute root mean square error 322 img1bands = img1.split() 323 img2bands = img2.split() 324 325 rmseVals = [] 326 imgBands = zip(img1bands, img2bands) 327 for img1Ch, img2Ch in imgBands: 328 imSz = numpy.prod(img1Ch.size) 329 dat1 = numpy.array(img1Ch) 330 dat2 = numpy.array(img2Ch) 331 332 sad = numpy.sum(numpy.square(dat1 - dat2)) 333 mse = numpy.divide(sad, imSz) 334 rmse = numpy.sqrt(mse) 335 rmseVals.append(rmse) 336 337 return rmseVals 338 339 @staticmethod 340 def get_color_refs(mode, corners): 341 """ 342 Build a set of reference colors from apriori color list. 343 344 Args: 345 mode (str): The color mode (LDR, or HDR) 346 corners (str or list): The corner or list of corners -- named TL, 347 TR, BL, and BR -- to return. 348 349 Returns: 350 tuple: The color value, if corners was a name. 351 [tuple]: List of color values, if corners was a list of names. 352 """ 353 modes = { 354 "LDR": ASTCENC_TEST_PATTERN_LDR, 355 "HDR": ASTCENC_TEST_PATTERN_HDR 356 } 357 358 if isinstance(corners, str): 359 return [modes[mode][corners]] 360 361 return [modes[mode][corner] for corner in corners] 362 363 def assertColorSame(self, colorRef, colorNew, threshold=0.02, swiz=None): 364 """ 365 Test if a color is the similar to a reference. 366 367 Will trigger a test failure if the colors are not within threshold. 368 369 Args: 370 colorRef (tuple): The reference color to compare with. 371 colorNew (tuple): The new color. 372 threshold (float): The allowed deviation from colorRef (ratio). 373 swiz (str): The swizzle string (4 characters from the set 374 `rgba01`), applied to the reference color. 375 """ 376 self.assertEqual(len(colorRef), len(colorNew)) 377 378 # Swizzle the reference color if needed 379 if swiz: 380 self.assertEqual(len(swiz), len(colorRef)) 381 382 remap = { 383 "0": len(colorRef), 384 "1": len(colorRef) + 1 385 } 386 387 if len(colorRef) >= 1: 388 remap["r"] = 0 389 if len(colorRef) >= 2: 390 remap["g"] = 1 391 if len(colorRef) >= 3: 392 remap["b"] = 2 393 if len(colorRef) >= 4: 394 remap["a"] = 3 395 396 colorRefExt = list(colorRef) + [0.0, 1.0] 397 colorRef = [colorRefExt[remap[s]] for s in swiz] 398 399 for chRef, chNew in zip(colorRef, colorNew): 400 deltaMax = chRef * threshold 401 self.assertAlmostEqual(chRef, chNew, delta=deltaMax) 402 403 def exec(self, command, pattern=None): 404 """ 405 Execute a positive test. 406 407 Will trigger a test failure if the subprocess return code is any value 408 other than zero. 409 410 Args: 411 command (list(str)): The command to execute. 412 pattern (re.Pattern): The regex pattern to search for, must 413 contain a single group (this is returned to the caller). The 414 test will fail if no pattern match is found. 415 416 Returns: 417 str: The stdout output of the child process, or the first group 418 from the passed regex pattern. 419 """ 420 try: 421 result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE, 422 universal_newlines=True, check=True) 423 error = False 424 except sp.CalledProcessError as ex: 425 result = ex 426 error = True 427 428 # Emit debug logging if needed 429 if ASTCENC_CLI_ALWAYS or (error and ASTCENC_CLI_ON_ERROR): 430 # Format for shell replay 431 print("\n" + " ".join(command)) 432 # Format for script command list replay 433 print("\n" + ", ".join(("\"%s\"" % x for x in command))) 434 435 if ASTCENC_LOG_ALWAYS or (error and ASTCENC_LOG_ON_ERROR): 436 print(result.stdout) 437 438 rcode = result.returncode 439 440 if rcode < 0: 441 msg = "Exec died with signal %s" % signal.Signals(-rcode).name 442 self.assertGreaterEqual(rcode, 0, msg) 443 444 if rcode > 0: 445 msg = "Exec died with application error %u" % rcode 446 self.assertEqual(rcode, 0, msg) 447 448 # If there is a regex pattern provided, then search for it 449 if pattern: 450 match = pattern.search(result.stdout) 451 self.assertIsNotNone(match) 452 return match.group(1) 453 454 return result.stdout 455 456 def test_ldr_compress(self): 457 """ 458 Test basic LDR compression. 459 """ 460 imIn = self.get_ref_image_path("LDR", "input", "A") 461 imOut = self.get_tmp_image_path("LDR", "comp") 462 imRef = self.get_ref_image_path("LDR", "comp", "A") 463 464 command = [self.binary, "-cl", imIn, imOut, "6x6", "-exhaustive"] 465 self.exec(command) 466 self.assertTrue(filecmp.cmp(imRef, imOut, False)) 467 468 def test_srgb_compress(self): 469 """ 470 Test basic LDR sRGB compression. 471 """ 472 imIn = self.get_ref_image_path("LDRS", "input", "A") 473 imOut = self.get_tmp_image_path("LDRS", "comp") 474 imRef = self.get_ref_image_path("LDRS", "comp", "A") 475 476 command = [self.binary, "-cs", imIn, imOut, "6x6", "-exhaustive"] 477 self.exec(command) 478 self.assertTrue(filecmp.cmp(imRef, imOut, False)) 479 480 def test_hdr_compress1(self): 481 """ 482 Test basic HDR + LDR alpha compression. 483 """ 484 imIn = self.get_ref_image_path("HDR", "input", "A") 485 imOut = self.get_tmp_image_path("HDR", "comp") 486 imRef = self.get_ref_image_path("HDR", "comp", "A") 487 488 command = [self.binary, "-ch", imIn, imOut, "6x6", "-exhaustive"] 489 self.exec(command) 490 self.assertTrue(filecmp.cmp(imRef, imOut, False)) 491 492 def test_hdr_compress2(self): 493 """ 494 Test basic HDR + HDR alpha compression. 495 """ 496 imIn = self.get_ref_image_path("HDR", "input", "A") 497 imOut = self.get_tmp_image_path("HDR", "comp") 498 imRef = self.get_ref_image_path("HDR", "comp", "A") 499 500 command = [self.binary, "-cH", imIn, imOut, "6x6", "-exhaustive"] 501 self.exec(command) 502 self.assertTrue(filecmp.cmp(imRef, imOut, False)) 503 504 def test_ldr_decompress(self): 505 """ 506 Test basic LDR decompression. 507 """ 508 imIn = self.get_ref_image_path("LDR", "comp", "A") 509 imOut = self.get_tmp_image_path("LDR", "decomp") 510 imRef = self.get_ref_image_path("LDR", "input", "A") 511 512 command = [self.binary, "-dl", imIn, imOut] 513 self.exec(command) 514 self.assertTrue(self.compare(imRef, imOut)) 515 516 def test_srgb_decompress(self): 517 """ 518 Test basic LDR sRGB decompression. 519 """ 520 imIn = self.get_ref_image_path("LDRS", "comp", "A") 521 imOut = self.get_tmp_image_path("LDRS", "decomp") 522 imRef = self.get_ref_image_path("LDRS", "input", "A") 523 524 command = [self.binary, "-ds", imIn, imOut] 525 self.exec(command) 526 self.assertTrue(self.compare(imRef, imOut)) 527 528 def test_hdr_decompress1(self): 529 """ 530 Test basic HDR + LDR alpha decompression. 531 """ 532 imIn = self.get_ref_image_path("HDR", "comp", "A") 533 imOut = self.get_tmp_image_path("HDR", "decomp") 534 imRef = self.get_ref_image_path("HDR", "input", "A") 535 536 command = [self.binary, "-dh", imIn, imOut] 537 self.exec(command) 538 539 colRef = tli.Image(imRef).get_colors((0, 0)) 540 colOut = tli.Image(imOut).get_colors((0, 0)) 541 self.assertColorSame(colRef, colOut) 542 543 def test_hdr_decompress2(self): 544 """ 545 Test basic HDR + HDR alpha decompression. 546 """ 547 imIn = self.get_ref_image_path("HDR", "comp", "A") 548 imOut = self.get_tmp_image_path("HDR", "decomp") 549 imRef = self.get_ref_image_path("HDR", "input", "A") 550 551 command = [self.binary, "-dH", imIn, imOut] 552 self.exec(command) 553 554 colRef = tli.Image(imRef).get_colors((0, 0)) 555 colOut = tli.Image(imOut).get_colors((0, 0)) 556 self.assertColorSame(colRef, colOut) 557 558 def test_ldr_roundtrip(self): 559 """ 560 Test basic LDR round-trip 561 """ 562 imIn = self.get_ref_image_path("LDR", "input", "A") 563 imOut = self.get_tmp_image_path("LDR", "decomp") 564 565 command = [self.binary, "-tl", imIn, imOut, "6x6", "-exhaustive"] 566 self.exec(command) 567 self.assertTrue(self.compare(imIn, imOut)) 568 569 def test_srgb_roundtrip(self): 570 """ 571 Test basic LDR sRGB round-trip 572 """ 573 imIn = self.get_ref_image_path("LDRS", "input", "A") 574 imOut = self.get_tmp_image_path("LDRS", "decomp") 575 576 command = [self.binary, "-ts", imIn, imOut, "6x6", "-exhaustive"] 577 self.exec(command) 578 self.assertTrue(self.compare(imIn, imOut)) 579 580 def test_hdr_roundtrip1(self): 581 """ 582 Test basic HDR + LDR alpha round-trip. 583 """ 584 imIn = self.get_ref_image_path("HDR", "input", "A") 585 imOut = self.get_tmp_image_path("HDR", "decomp") 586 587 command = [self.binary, "-th", imIn, imOut, "6x6", "-exhaustive"] 588 self.exec(command) 589 colIn = tli.Image(imIn).get_colors((0, 0)) 590 colOut = tli.Image(imOut).get_colors((0, 0)) 591 self.assertColorSame(colIn, colOut) 592 593 def test_hdr_roundtrip2(self): 594 """ 595 Test basic HDR + HDR alpha round-trip. 596 """ 597 imIn = self.get_ref_image_path("HDR", "input", "A") 598 imOut = self.get_tmp_image_path("HDR", "decomp") 599 600 command = [self.binary, "-tH", imIn, imOut, "6x6", "-exhaustive"] 601 self.exec(command) 602 colIn = tli.Image(imIn).get_colors((0, 0)) 603 colOut = tli.Image(imOut).get_colors((0, 0)) 604 self.assertColorSame(colIn, colOut) 605 606 def test_valid_2d_block_sizes(self): 607 """ 608 Test all valid block sizes are accepted (2D images). 609 """ 610 blockSizes = [ 611 "4x4", "5x4", "5x5", "6x5", "6x6", "8x5", "8x6", 612 "10x5", "10x6", "8x8", "10x8", "10x10", "12x10", "12x12" 613 ] 614 615 imIn = self.get_ref_image_path("LDR", "input", "A") 616 imOut = self.get_tmp_image_path("LDR", "decomp") 617 618 for blk in blockSizes: 619 with self.subTest(blockSize=blk): 620 command = [self.binary, "-tl", imIn, imOut, blk, "-exhaustive"] 621 self.exec(command) 622 colIn = tli.Image(imIn).get_colors((0, 0)) 623 colOut = tli.Image(imOut).get_colors((0, 0)) 624 self.assertColorSame(colIn, colOut) 625 626 def test_valid_3d_block_sizes(self): 627 """ 628 Test all valid block sizes are accepted (3D images). 629 """ 630 blockSizes = [ 631 "3x3x3", 632 "4x3x3", "4x4x3", "4x4x4", 633 "5x4x4", "5x5x4", "5x5x5", 634 "6x5x5", "6x6x5", "6x6x6" 635 ] 636 637 imIn = self.get_ref_image_path("LDR", "input", "A") 638 imOut = self.get_tmp_image_path("LDR", "decomp") 639 640 for blk in blockSizes: 641 with self.subTest(blockSize=blk): 642 command = [self.binary, "-tl", imIn, imOut, blk, "-exhaustive"] 643 self.exec(command) 644 colIn = tli.Image(imIn).get_colors((0, 0)) 645 colOut = tli.Image(imOut).get_colors((0, 0)) 646 self.assertColorSame(colIn, colOut) 647 648 def test_valid_presets(self): 649 """ 650 Test all valid presets are accepted 651 """ 652 presets = ["-fastest", "-fast", "-medium", 653 "-thorough", "-verythorough", "-exhaustive"] 654 655 imIn = self.get_ref_image_path("LDR", "input", "A") 656 imOut = self.get_tmp_image_path("LDR", "decomp") 657 658 for preset in presets: 659 with self.subTest(preset=preset): 660 command = [self.binary, "-tl", imIn, imOut, "4x4", preset] 661 self.exec(command) 662 colIn = tli.Image(imIn).get_colors((0, 0)) 663 colOut = tli.Image(imOut).get_colors((0, 0)) 664 self.assertColorSame(colIn, colOut) 665 666 def test_valid_ldr_input_formats(self): 667 """ 668 Test valid LDR input file formats. 669 """ 670 imgFormats = ["bmp", "dds", "jpg", "ktx", "png", "tga"] 671 672 for imgFormat in imgFormats: 673 with self.subTest(imgFormat=imgFormat): 674 imIn = "./Test/Data/Tiles/ldr.%s" % imgFormat 675 imOut = self.get_tmp_image_path("LDR", "decomp") 676 677 command = [self.binary, "-tl", imIn, imOut, "4x4", "-fast"] 678 self.exec(command) 679 680 # Check colors if image wrapper supports it 681 if tli.Image.is_format_supported(imgFormat): 682 colIn = tli.Image(imIn).get_colors((7, 7)) 683 colOut = tli.Image(imOut).get_colors((7, 7)) 684 685 # Catch exception and add fallback for tga handling 686 # having unstable origin in ImageMagick 687 try: 688 self.assertColorSame(colIn, colOut) 689 continue 690 except AssertionError as ex: 691 if imgFormat != "tga": 692 raise ex 693 694 # Try yflipped TGA image 695 colIn = tli.Image(imIn).get_colors((7, 7)) 696 colOut = tli.Image(imOut).get_colors((7, 1)) 697 self.assertColorSame(colIn, colOut) 698 699 def test_valid_uncomp_ldr_output_formats(self): 700 """ 701 Test valid uncompressed LDR output file formats. 702 """ 703 imgFormats = ["bmp", "dds", "ktx", "png", "tga"] 704 705 for imgFormat in imgFormats: 706 with self.subTest(imgFormat=imgFormat): 707 imIn = self.get_ref_image_path("LDR", "input", "A") 708 imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) 709 710 command = [self.binary, "-tl", imIn, imOut, "4x4", "-fast"] 711 self.exec(command) 712 713 # Check colors if image wrapper supports it 714 if tli.Image.is_format_supported(imgFormat): 715 colIn = tli.Image(imIn).get_colors((7, 7)) 716 colOut = tli.Image(imOut).get_colors((7, 7)) 717 self.assertColorSame(colIn, colOut) 718 719 def test_valid_comp_ldr_output_formats(self): 720 """ 721 Test valid compressed LDR output file formats. 722 """ 723 imgFormats = ["astc", "ktx"] 724 725 for imgFormat in imgFormats: 726 with self.subTest(imgFormat=imgFormat): 727 imIn = self.get_ref_image_path("LDR", "input", "A") 728 imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) 729 imOut2 = self.get_tmp_image_path("LDR", "decomp") 730 731 command = [self.binary, "-cl", imIn, imOut, "4x4", "-fast"] 732 self.exec(command) 733 734 command = [self.binary, "-dl", imOut, imOut2] 735 self.exec(command) 736 737 # Check colors if image wrapper supports it 738 if tli.Image.is_format_supported(imgFormat): 739 colIn = tli.Image(imIn).get_colors((7, 7)) 740 colOut = tli.Image(imOut2).get_colors((7, 7)) 741 self.assertColorSame(colIn, colOut2) 742 743 def test_valid_hdr_input_formats(self): 744 """ 745 Test valid HDR input file formats. 746 """ 747 imgFormats = ["exr", "hdr"] 748 749 for imgFormat in imgFormats: 750 with self.subTest(imgFormat=imgFormat): 751 imIn = "./Test/Data/Tiles/hdr.%s" % imgFormat 752 imOut = self.get_tmp_image_path("HDR", "decomp") 753 754 command = [self.binary, "-th", imIn, imOut, "4x4", "-fast"] 755 self.exec(command) 756 757 # Check colors if image wrapper supports it 758 if tli.Image.is_format_supported(imgFormat, profile="hdr"): 759 colIn = tli.Image(imIn).get_colors((7, 7)) 760 colOut = tli.Image(imOut).get_colors((7, 7)) 761 self.assertColorSame(colIn, colOut) 762 763 def test_valid_uncomp_hdr_output_formats(self): 764 """ 765 Test valid uncompressed HDR output file formats. 766 """ 767 imgFormats = ["dds", "exr", "hdr", "ktx"] 768 769 for imgFormat in imgFormats: 770 with self.subTest(imgFormat=imgFormat): 771 imIn = self.get_ref_image_path("HDR", "input", "A") 772 imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) 773 774 command = [self.binary, "-th", imIn, imOut, "4x4", "-fast"] 775 self.exec(command) 776 777 # Check colors if image wrapper supports it 778 if tli.Image.is_format_supported(imgFormat, profile="hdr"): 779 colIn = tli.Image(imIn).get_colors((7, 7)) 780 colOut = tli.Image(imOut).get_colors((7, 7)) 781 self.assertColorSame(colIn, colOut) 782 783 def test_valid_comp_hdr_output_formats(self): 784 """ 785 Test valid compressed HDR output file formats. 786 """ 787 imgFormats = ["astc", "ktx"] 788 789 for imgFormat in imgFormats: 790 with self.subTest(imgFormat=imgFormat): 791 imIn = self.get_ref_image_path("HDR", "input", "A") 792 imOut = self.get_tmp_image_path("EXP", ".%s" % imgFormat) 793 imOut2 = self.get_tmp_image_path("HDR", "decomp") 794 795 command = [self.binary, "-ch", imIn, imOut, "4x4", "-fast"] 796 self.exec(command) 797 798 command = [self.binary, "-dh", imOut, imOut2] 799 self.exec(command) 800 801 # Check colors if image wrapper supports it 802 if tli.Image.is_format_supported(imgFormat): 803 colIn = tli.Image(imIn).get_colors((7, 7)) 804 colOut = tli.Image(imOut2).get_colors((7, 7)) 805 self.assertColorSame(colIn, colOut2) 806 807 def test_compress_normal_psnr(self): 808 """ 809 Test compression of normal textures using PSNR error metrics. 810 """ 811 decompFile = self.get_tmp_image_path("LDR", "decomp") 812 813 command = [ 814 self.binary, "-tl", 815 "./Test/Images/Small/LDR-XY/ldr-xy-00.png", 816 decompFile, "5x5", "-exhaustive"] 817 818 refdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) 819 820 command.append("-normal") 821 testdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) 822 823 # Note that this test simply asserts that the "-normal_psnr" is 824 # connected and affects the output. We don't test it does something 825 # useful; that it outside the scope of this test case. 826 self.assertNotEqual(refdB, testdB) 827 828 def test_compress_normal_percep(self): 829 """ 830 Test compression of normal textures using perceptual error metrics. 831 """ 832 decompFile = self.get_tmp_image_path("LDR", "decomp") 833 834 command = [ 835 self.binary, "-tl", 836 "./Test/Images/Small/LDR-XY/ldr-xy-00.png", 837 decompFile, "4x4", "-exhaustive"] 838 839 refdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) 840 841 command.append("-normal") 842 command.append("-perceptual") 843 testdB = float(self.exec(command, LDR_RGB_PSNR_PATTERN)) 844 845 # Note that this test simply asserts that the "-normal -percep" is 846 # connected and affects the output. We don't test it does something 847 # useful; that it outside the scope of this test case. 848 self.assertNotEqual(refdB, testdB) 849 850 def test_compress_esw(self): 851 """ 852 Test compression swizzles. 853 """ 854 # The swizzles to test 855 swizzles = ["rgba", "g0r1", "rrrg"] 856 857 # Compress a swizzled image 858 for swizzle in swizzles: 859 with self.subTest(swizzle=swizzle): 860 decompFile = self.get_tmp_image_path("LDR", "decomp") 861 862 command = [ 863 self.binary, "-tl", 864 "./Test/Data/Tiles/ldr.png", 865 decompFile, "4x4", "-exhaustive", 866 "-esw", swizzle] 867 868 self.exec(command) 869 870 # Fetch the three color 871 img = tli.Image(decompFile) 872 colorVal = img.get_colors([(7, 7)]) 873 colorRef = self.get_color_refs("LDR", "BR") 874 self.assertColorSame(colorRef[0], colorVal[0], swiz=swizzle) 875 876 def test_compress_dsw(self): 877 """ 878 Test decompression swizzles. 879 """ 880 # The swizzles to test 881 swizzles = ["rgba", "g0r1", "rrrg"] 882 883 # Decompress a swizzled image 884 for swizzle in swizzles: 885 with self.subTest(swizzle=swizzle): 886 decompFile = self.get_tmp_image_path("LDR", "decomp") 887 888 command = [ 889 self.binary, "-tl", 890 "./Test/Data/Tiles/ldr.png", 891 decompFile, "4x4", "-exhaustive", 892 "-dsw", swizzle] 893 894 self.exec(command) 895 896 # Fetch the three color 897 img = tli.Image(decompFile) 898 colorVal = img.get_colors([(7, 7)]) 899 colorRef = self.get_color_refs("LDR", "BR") 900 self.assertColorSame(colorRef[0], colorVal[0], swiz=swizzle) 901 902 def test_compress_esw_dsw(self): 903 """ 904 Test compression and decompression swizzles 905 """ 906 # Compress a swizzled image, and swizzle back in decompression 907 decompFile = self.get_tmp_image_path("LDR", "decomp") 908 909 command = [ 910 self.binary, "-tl", 911 "./Test/Data/Tiles/ldr.png", 912 decompFile, "4x4", "-exhaustive", 913 "-esw", "gbar", "-dsw", "argb"] 914 915 self.exec(command) 916 917 # Fetch the three color 918 img = tli.Image(decompFile) 919 colorVal = img.get_colors([(7, 7)]) 920 colorRef = self.get_color_refs("LDR", "BR") 921 self.assertColorSame(colorRef[0], colorVal[0]) 922 923 def test_compress_flip(self): 924 """ 925 Test LDR image flip on compression. 926 """ 927 # Compress a flipped image 928 compFile = self.get_tmp_image_path("LDR", "comp") 929 930 command = [ 931 self.binary, "-cl", 932 "./Test/Data/Tiles/ldr.png", 933 compFile, "4x4", "-fast", "-yflip"] 934 935 self.exec(command) 936 937 # Decompress a non-flipped image 938 decompFile = self.get_tmp_image_path("LDR", "decomp") 939 940 command = [ 941 self.binary, "-dl", 942 compFile, 943 decompFile] 944 945 self.exec(command) 946 947 # Compare TL (0, 0) with BL - should match 948 colorRef = self.get_color_refs("LDR", "BL") 949 950 img = tli.Image(decompFile) 951 colorVal = img.get_colors([(0, 0)]) 952 self.assertColorSame(colorRef[0], colorVal[0]) 953 954 def test_decompress_flip(self): 955 """ 956 Test LDR image flip on decompression. 957 """ 958 # Compress a non-flipped image 959 compFile = self.get_tmp_image_path("LDR", "comp") 960 961 command = [ 962 self.binary, "-cl", 963 "./Test/Data/Tiles/ldr.png", 964 compFile, "4x4", "-fast"] 965 966 self.exec(command) 967 968 # Decompress a flipped image 969 decompFile = self.get_tmp_image_path("LDR", "decomp") 970 971 command = [ 972 self.binary, "-dl", 973 compFile, 974 decompFile, "-yflip"] 975 976 self.exec(command) 977 978 # Compare TL (0, 0) with BL - should match 979 colorRef = self.get_color_refs("LDR", "BL") 980 981 img = tli.Image(decompFile) 982 colorVal = img.get_colors([(0, 0)]) 983 self.assertColorSame(colorRef[0], colorVal[0]) 984 985 def test_roundtrip_flip(self): 986 """ 987 Test LDR image flip on roundtrip (no flip should occur). 988 """ 989 # Compress and decompressed a flipped LDR image 990 decompFile = self.get_tmp_image_path("LDR", "decomp") 991 992 command = [ 993 self.binary, "-tl", 994 "./Test/Data/Tiles/ldr.png", 995 decompFile, "4x4", "-fast", "-yflip"] 996 997 self.exec(command) 998 999 # Compare TL (0, 0) with TL - should match - i.e. no flip 1000 colorRef = self.get_color_refs("LDR", "TL") 1001 1002 img = tli.Image(decompFile) 1003 colorVal = img.get_colors([(0, 0)]) 1004 1005 self.assertColorSame(colorRef[0], colorVal[0]) 1006 1007 def test_channel_weighting(self): 1008 """ 1009 Test channel weighting. 1010 """ 1011 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1012 decompFile = self.get_tmp_image_path("LDR", "decomp") 1013 1014 # Compute the basic image without any channel weights 1015 command = [ 1016 self.binary, "-tl", 1017 inputFile, decompFile, "4x4", "-medium"] 1018 1019 self.exec(command) 1020 baseRMSE = self.get_channel_rmse(inputFile, decompFile) 1021 1022 # Note: Using -cw can result in a worse result than not using -cw, 1023 # with regressions in RMSE for the high-weighted channel. This is 1024 # particularly an issue in synthetic images, as they are more likely to 1025 # hit corner cases in the heuristics. It happens to "work" for the 1026 # selected test image and these settings, but might start to fail in 1027 # future due to compressor changes. 1028 1029 # Test each channel with a high weight 1030 for chIdx, chName in ((0, "R"), (1, "G"), (2, "B"), (3, "A")): 1031 with self.subTest(channel=chName): 1032 cwArg = ["%s" % (10 if x == chIdx else 1) for x in range(0, 4)] 1033 command2 = command + ["-cw"] + cwArg 1034 self.exec(command2) 1035 chRMSE = self.get_channel_rmse(inputFile, decompFile) 1036 self.assertLess(chRMSE[chIdx], baseRMSE[chIdx]) 1037 1038 def test_partition_count_limit(self): 1039 """ 1040 Test partition count limit. 1041 """ 1042 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1043 decompFile = self.get_tmp_image_path("LDR", "decomp") 1044 1045 # Compute the basic image without any channel weights 1046 command = [ 1047 self.binary, "-tl", 1048 inputFile, decompFile, "4x4", "-medium"] 1049 1050 self.exec(command) 1051 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1052 1053 command += ["-partitioncountlimit", "1"] 1054 self.exec(command) 1055 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1056 1057 # RMSE should get worse (higher) if we reduce search space 1058 self.assertGreater(testRMSE, refRMSE) 1059 1060 def test_2partition_index_limit(self): 1061 """ 1062 Test partition index limit. 1063 """ 1064 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1065 decompFile = self.get_tmp_image_path("LDR", "decomp") 1066 1067 # Compute the basic image without any channel weights 1068 command = [ 1069 self.binary, "-tl", 1070 inputFile, decompFile, "4x4", "-medium"] 1071 1072 self.exec(command) 1073 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1074 1075 command += ["-2partitionindexlimit", "1"] 1076 self.exec(command) 1077 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1078 1079 # RMSE should get worse (higher) if we reduce search space 1080 self.assertGreater(testRMSE, refRMSE) 1081 1082 def test_3partition_index_limit(self): 1083 """ 1084 Test partition index limit. 1085 """ 1086 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1087 decompFile = self.get_tmp_image_path("LDR", "decomp") 1088 1089 # Compute the basic image without any channel weights 1090 command = [ 1091 self.binary, "-tl", 1092 inputFile, decompFile, "4x4", "-medium"] 1093 1094 self.exec(command) 1095 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1096 1097 command += ["-3partitionindexlimit", "1"] 1098 self.exec(command) 1099 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1100 1101 # RMSE should get worse (higher) if we reduce search space 1102 self.assertGreater(testRMSE, refRMSE) 1103 1104 def test_4partition_index_limit(self): 1105 """ 1106 Test partition index limit. 1107 """ 1108 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1109 decompFile = self.get_tmp_image_path("LDR", "decomp") 1110 1111 # Compute the basic image without any channel weights 1112 command = [ 1113 self.binary, "-tl", 1114 inputFile, decompFile, "4x4", "-medium"] 1115 1116 self.exec(command) 1117 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1118 1119 command += ["-4partitionindexlimit", "1"] 1120 self.exec(command) 1121 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1122 1123 # RMSE should get worse (higher) if we reduce search space 1124 self.assertGreater(testRMSE, refRMSE) 1125 1126 def test_blockmode_limit(self): 1127 """ 1128 Test block mode limit. 1129 """ 1130 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1131 decompFile = self.get_tmp_image_path("LDR", "decomp") 1132 1133 # Compute the basic image without any channel weights 1134 command = [ 1135 self.binary, "-tl", 1136 inputFile, decompFile, "4x4", "-medium"] 1137 1138 self.exec(command) 1139 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1140 1141 command += ["-blockmodelimit", "25"] 1142 self.exec(command) 1143 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1144 1145 # RMSE should get worse (higher) if we reduce search space 1146 self.assertGreater(testRMSE, refRMSE) 1147 1148 def test_refinement_limit(self): 1149 """ 1150 Test refinement limit. 1151 """ 1152 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1153 decompFile = self.get_tmp_image_path("LDR", "decomp") 1154 1155 command = [ 1156 self.binary, "-tl", 1157 inputFile, decompFile, "4x4", "-medium"] 1158 1159 self.exec(command) 1160 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1161 1162 command += ["-refinementlimit", "1"] 1163 self.exec(command) 1164 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1165 1166 # RMSE should get worse (higher) if we reduce search space 1167 self.assertGreater(testRMSE, refRMSE) 1168 1169 def test_candidate_limit(self): 1170 """ 1171 Test candidate limit. 1172 """ 1173 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1174 decompFile = self.get_tmp_image_path("LDR", "decomp") 1175 1176 command = [ 1177 self.binary, "-tl", 1178 inputFile, decompFile, "4x4", "-medium"] 1179 1180 self.exec(command) 1181 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1182 1183 command += ["-candidatelimit", "1"] 1184 self.exec(command) 1185 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1186 1187 # RMSE should get worse (higher) if we reduce search space 1188 self.assertGreater(testRMSE, refRMSE) 1189 1190 def test_db_cutoff_limit(self): 1191 """ 1192 Test db cutoff limit. 1193 """ 1194 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1195 decompFile = self.get_tmp_image_path("LDR", "decomp") 1196 1197 # Compute the basic image without any channel weights 1198 command = [ 1199 self.binary, "-tl", 1200 inputFile, decompFile, "4x4", "-medium"] 1201 1202 self.exec(command) 1203 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1204 1205 command += ["-dblimit", "10"] 1206 self.exec(command) 1207 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1208 1209 # RMSE should get worse (higher) if we reduce cutoff quality 1210 self.assertGreater(testRMSE, refRMSE) 1211 1212 def test_2partition_early_limit(self): 1213 """ 1214 Test 2 partition early limit. 1215 """ 1216 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1217 decompFile = self.get_tmp_image_path("LDR", "decomp") 1218 1219 # Compute the basic image without any channel weights 1220 command = [ 1221 self.binary, "-tl", 1222 inputFile, decompFile, "4x4", "-medium"] 1223 1224 self.exec(command) 1225 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1226 1227 command += ["-2partitionlimitfactor", "1.0"] 1228 self.exec(command) 1229 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1230 1231 # RMSE should get worse (higher) if we reduce search space 1232 self.assertGreater(testRMSE, refRMSE) 1233 1234 def test_3partition_early_limit(self): 1235 """ 1236 Test 3 partition early limit. 1237 """ 1238 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1239 decompFile = self.get_tmp_image_path("LDR", "decomp") 1240 1241 # Compute the basic image without any channel weights 1242 command = [ 1243 self.binary, "-tl", 1244 inputFile, decompFile, "4x4", "-medium"] 1245 1246 self.exec(command) 1247 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1248 1249 command += ["-3partitionlimitfactor", "1.0"] 1250 self.exec(command) 1251 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1252 1253 # RMSE should get worse (higher) if we reduce search space 1254 self.assertNotEqual(testRMSE, refRMSE) 1255 1256 def test_2plane_correlation_limit(self): 1257 """ 1258 Test 2 plane correlation limit. 1259 """ 1260 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1261 decompFile = self.get_tmp_image_path("LDR", "decomp") 1262 1263 # Compute the basic image without any channel weights 1264 command = [ 1265 self.binary, "-tl", 1266 inputFile, decompFile, "4x4", "-medium"] 1267 1268 self.exec(command) 1269 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1270 1271 command += ["-2planelimitcorrelation", "0.1"] 1272 self.exec(command) 1273 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1274 1275 # RMSE should get worse (higher) if we reduce search space 1276 self.assertGreater(testRMSE, refRMSE) 1277 1278 def test_2partition_candidate_limit(self): 1279 """ 1280 Test 2 partition partitioning candidate limit. 1281 """ 1282 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1283 decompFile = self.get_tmp_image_path("LDR", "decomp") 1284 1285 # Compute the basic image without any channel weights 1286 command = [ 1287 self.binary, "-tl", 1288 inputFile, decompFile, "4x4", "-medium"] 1289 1290 self.exec(command) 1291 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1292 1293 command += ["-2partitioncandidatelimit", "1"] 1294 self.exec(command) 1295 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1296 1297 # RMSE should get worse (higher) if we reduce search space 1298 self.assertGreater(testRMSE, refRMSE) 1299 1300 def test_3partition_candidate_limit(self): 1301 """ 1302 Test 3 partition partitioning candidate limit. 1303 """ 1304 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1305 decompFile = self.get_tmp_image_path("LDR", "decomp") 1306 1307 # Compute the basic image without any channel weights 1308 command = [ 1309 self.binary, "-tl", 1310 inputFile, decompFile, "4x4", "-medium"] 1311 1312 self.exec(command) 1313 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1314 1315 command += ["-3partitioncandidatelimit", "1"] 1316 self.exec(command) 1317 testRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1318 1319 # RMSE should get worse (higher) if we reduce search space 1320 self.assertGreater(testRMSE, refRMSE) 1321 1322 def test_4partition_candidate_limit(self): 1323 """ 1324 Test 4 partition partitioning candidate limit. 1325 """ 1326 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1327 decompFile = self.get_tmp_image_path("LDR", "decomp") 1328 1329 # Compute the basic image without any channel weights 1330 command = [ 1331 self.binary, "-tl", 1332 inputFile, decompFile, "4x4", "-medium"] 1333 1334 self.exec(command) 1335 refRMSE = sum(self.get_channel_rmse(inputFile, decompFile)) 1336 1337 command += ["-4partitioncandidatelimit", "1"] 1338 self.exec(command) 1339 1340 # RMSE should get worse (higher) if we reduce search space 1341 # Don't check this here, as 4 partitions not used in any Small image 1342 # even for -exhaustive, BUT command line option must be accepted and 1343 # not error ... 1344 # self.assertGreater(testRMSE, refRMSE) 1345 1346 @unittest.skipIf(os.cpu_count() == 1, "Cannot test on single core host") 1347 def test_thread_count(self): 1348 """ 1349 Test codec thread count. 1350 """ 1351 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1352 decompFile = self.get_tmp_image_path("LDR", "decomp") 1353 1354 # Compute the basic image without any channel weights 1355 command = [ 1356 self.binary, "-tl", 1357 inputFile, decompFile, "4x4", "-medium"] 1358 1359 start = time.time() 1360 self.exec(command) 1361 refTime = time.time() - start 1362 1363 command += ["-j", "1"] 1364 start = time.time() 1365 self.exec(command) 1366 testTime = time.time() - start 1367 1368 # Test time should get slower with fewer threads 1369 self.assertGreater(testTime, refTime) 1370 1371 def test_silent(self): 1372 """ 1373 Test silent 1374 """ 1375 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1376 decompFile = self.get_tmp_image_path("LDR", "decomp") 1377 1378 # Compute the basic image without any channel weights 1379 command = [ 1380 self.binary, "-tl", 1381 inputFile, decompFile, "4x4", "-medium"] 1382 stdout = self.exec(command) 1383 1384 command += ["-silent"] 1385 stdoutSilent = self.exec(command) 1386 1387 # Check that stdout is shorter in silent mode. Note that this doesn't 1388 # check that it is as silent as it should be, just that silent is wired 1389 # somewhere ... 1390 self.assertLess(len(stdoutSilent), len(stdout)) 1391 1392 def test_image_quality_stability(self): 1393 """ 1394 Test that a round-trip and a file-based round-trip give same result. 1395 """ 1396 inputFile = "./Test/Images/Small/LDR-RGBA/ldr-rgba-00.png" 1397 p1DecFile = self.get_tmp_image_path("LDR", "decomp") 1398 p2CompFile = self.get_tmp_image_path("LDR", "comp") 1399 p2DecFile = self.get_tmp_image_path("LDR", "decomp") 1400 1401 # Compute the first image using a direct round-trip 1402 command = [self.binary, "-tl", inputFile, p1DecFile, "4x4", "-medium"] 1403 self.exec(command) 1404 1405 # Compute the first image using a file-based round-trip 1406 command = [self.binary, "-cl", inputFile, p2CompFile, "4x4", "-medium", 1407 "-decode_unorm8"] 1408 self.exec(command) 1409 command = [self.binary, "-dl", p2CompFile, p2DecFile] 1410 self.exec(command) 1411 1412 # RMSE should be the same 1413 p1RMSE = sum(self.get_channel_rmse(inputFile, p1DecFile)) 1414 p2RMSE = sum(self.get_channel_rmse(inputFile, p2DecFile)) 1415 self.assertEqual(p1RMSE, p2RMSE) 1416 1417 1418class CLINTest(CLITestBase): 1419 """ 1420 Command line interface negative tests. 1421 1422 These tests are designed to test that bad inputs to the command line are 1423 handled cleanly and that errors are correctly thrown. 1424 1425 Note that many tests are mutations of a valid positive test command line, 1426 to ensure that the base command line is valid before it is mutated many 1427 of these tests include a *positive test* to ensure that the starting point 1428 is actually a valid command line (otherwise we could be throwing an 1429 arbitrary error). 1430 """ 1431 1432 def exec(self, command, expectPass=False): 1433 """ 1434 Execute a negative test. 1435 1436 Test will automatically fail if: 1437 1438 * The subprocess return code is zero, unless ``expectPass==True``. 1439 * The subprocess correctly returned non-zero, but without any error 1440 message. 1441 * The subprocess dies with any kind of signal. 1442 1443 Args: 1444 command (list(str)): The command to execute. 1445 expectPass (bool): ``True`` if this command is actually expected to 1446 pass, which is used to validate commands before mutating them. 1447 """ 1448 try: 1449 result = sp.run(command, stdout=sp.PIPE, stderr=sp.PIPE, 1450 universal_newlines=True, check=True) 1451 error = False 1452 except sp.CalledProcessError as ex: 1453 # Pop out of the CPE scope to handle the error, as this reduces 1454 # test log verbosity on failure by avoiding nested exceptions 1455 result = ex 1456 error = True 1457 1458 rcode = result.returncode 1459 1460 # Emit debug logging if needed (negative rcode is a signal) 1461 badResult = (error == expectPass) or (rcode < 0) 1462 1463 if ASTCENC_CLI_ALWAYS or (badResult and ASTCENC_CLI_ON_ERROR_NEG): 1464 # Format for shell replay 1465 print("\n" + " ".join(command)) 1466 # Format for script command list replay 1467 print("\n" + ", ".join(("\"%s\"" % x for x in command))) 1468 1469 if ASTCENC_LOG_ALWAYS or (badResult and ASTCENC_LOG_ON_ERROR_NEG): 1470 print(result.stdout) 1471 1472 # If we expected a pass, then rcode == 0 1473 if expectPass: 1474 self.assertEqual(rcode, 0, "Exec did not pass as expected") 1475 self.assertNotIn("ERROR", result.stderr) 1476 return 1477 1478 # If we got a negative that's always bad (signal of some kind) 1479 if rcode < 0: 1480 msg = "Exec died with signal %s" % signal.Signals(-rcode).name 1481 self.assertGreaterEqual(rcode, 0, msg) 1482 1483 # Otherwise just assert that we got an error log, and some positive 1484 # return code value was returned 1485 self.assertIn("ERROR", result.stderr) 1486 self.assertGreater(rcode, 0, "Exec did not fail as expected") 1487 1488 def exec_with_omit(self, command, startOmit): 1489 """ 1490 Execute a negative test with command line argument omission. 1491 1492 These tests aim to prove that the command fails if arguments are 1493 missing. However the passed command MUST be a valid command which 1494 passes if no argument are omitted (this is checked, to ensure that 1495 the test case is a valid test). 1496 1497 Test will automatically fail if: 1498 1499 * A partial command doesn't fail. 1500 * The full command doesn't pass. 1501 """ 1502 # Run the command, incrementally omitting arguments 1503 commandLen = len(command) 1504 for subLen in range(startOmit, commandLen + 1): 1505 omit = len(command) - subLen 1506 with self.subTest(omit=omit): 1507 testCommand = command[:subLen] 1508 expectPass = omit == 0 1509 self.exec(testCommand, expectPass) 1510 1511 def test_cl_missing_args(self): 1512 """ 1513 Test -cl with missing arguments. 1514 """ 1515 # Build a valid command 1516 command = [ 1517 self.binary, "-cl", 1518 self.get_ref_image_path("LDR", "input", "A"), 1519 self.get_tmp_image_path("LDR", "comp"), 1520 "4x4", "-fast"] 1521 1522 self.exec_with_omit(command, 2) 1523 1524 def test_cl_missing_input(self): 1525 """ 1526 Test -cl with a missing input file. 1527 """ 1528 # Build a valid command with a missing input file 1529 command = [ 1530 self.binary, "-cl", 1531 "./Test/Data/missing.png", 1532 self.get_tmp_image_path("LDR", "comp"), 1533 "4x4", "-fast"] 1534 1535 self.exec(command) 1536 1537 def test_cl_missing_input_array_slice(self): 1538 """ 1539 Test -cl with a missing input file in an array slice. 1540 """ 1541 # Build a valid command with a missing input file 1542 command = [ 1543 self.binary, "-cl", 1544 "./Test/Data/Tiles/ldr.png", 1545 self.get_tmp_image_path("LDR", "comp"), 1546 "3x3x3", "-fast", "-zdim", "3"] 1547 1548 self.exec(command) 1549 1550 def test_cl_unknown_input(self): 1551 """ 1552 Test -cl with an unknown input file extension. 1553 """ 1554 # Build an otherwise valid command with the test flaw 1555 command = [ 1556 self.binary, "-cl", 1557 "./Test/Data/empty.unk", 1558 self.get_tmp_image_path("LDR", "comp"), 1559 "4x4", "-fast"] 1560 1561 self.exec(command) 1562 1563 def test_cl_missing_output(self): 1564 """ 1565 Test -cl with a missing output directory. 1566 """ 1567 # Build an otherwise valid command with the test flaw 1568 command = [ 1569 self.binary, "-cl", 1570 self.get_ref_image_path("LDR", "input", "A"), 1571 "./DoesNotExist/test.astc", 1572 "4x4", "-fast"] 1573 1574 self.exec(command) 1575 1576 def test_cl_unknown_output(self): 1577 """ 1578 Test -cl with an unknown output file extension. 1579 """ 1580 # Build an otherwise valid command with the test flaw 1581 command = [ 1582 self.binary, "-cl", 1583 self.get_ref_image_path("LDR", "input", "A"), 1584 "./test.aastc", 1585 "4x4", "-fast"] 1586 1587 self.exec(command) 1588 1589 def test_cl_bad_block_size(self): 1590 """ 1591 Test -cl with an invalid block size. 1592 """ 1593 badSizes = [ 1594 "4x5", # Illegal 2D block size 1595 "3x3x4", # Illegal 3D block size 1596 "4x4x4x4", # Too many dimensions 1597 "4x", # Incomplete 2D block size 1598 "4x4x", # Incomplete 3D block size 1599 "4x4x4x", # Over-long 3D block size 1600 "4xe", # Illegal non-numeric character 1601 "4x4e" # Additional non-numeric character 1602 ] 1603 1604 # Build an otherwise valid command with the test flaw 1605 command = [ 1606 self.binary, "-cl", 1607 self.get_ref_image_path("LDR", "input", "A"), 1608 self.get_tmp_image_path("LDR", "comp"), 1609 "4x4", "-fast"] 1610 1611 # Test that the underlying command is valid 1612 self.exec(command, True) 1613 1614 blockIndex = command.index("4x4") 1615 for badSize in badSizes: 1616 with self.subTest(blockSize=badSize): 1617 command[blockIndex] = badSize 1618 self.exec(command) 1619 1620 def test_cl_bad_preset(self): 1621 """ 1622 Test -cl with an invalid encoding preset. 1623 """ 1624 # Build an otherwise valid command with the test flaw 1625 command = [ 1626 self.binary, "-cl", 1627 self.get_ref_image_path("LDR", "input", "A"), 1628 self.get_tmp_image_path("LDR", "comp"), 1629 "4x4", "-fastt"] 1630 1631 self.exec(command) 1632 1633 def test_cl_bad_argument(self): 1634 """ 1635 Test -cl with an unknown additional argument. 1636 """ 1637 # Build an otherwise valid command with the test flaw 1638 command = [ 1639 self.binary, "-cl", 1640 self.get_ref_image_path("LDR", "input", "A"), 1641 self.get_tmp_image_path("LDR", "comp"), 1642 "4x4", "-fast", "-unknown"] 1643 1644 self.exec(command) 1645 1646 def test_cl_2d_block_with_array(self): 1647 """ 1648 Test -cl with a 2D block size and 3D input data. 1649 """ 1650 # Build an otherwise valid command with the test flaw 1651 1652 # TODO: This fails late (i.e. the data is still loaded, and we fail 1653 # at processing time when we see a 3D array). We could fail earlier at 1654 # parse time, which might consolidate the error handling code. 1655 command = [ 1656 self.binary, "-cl", 1657 "./Test/Data/Tiles/ldr.png", 1658 self.get_tmp_image_path("LDR", "comp"), 1659 "4x4", "-fast", "-zdim", "2"] 1660 1661 self.exec(command) 1662 1663 def test_cl_array_missing_args(self): 1664 """ 1665 Test -cl with a 2D block size and 3D input data. 1666 """ 1667 # Build an otherwise valid command 1668 command = [ 1669 self.binary, "-cl", 1670 "./Test/Data/Tiles/ldr.png", 1671 self.get_tmp_image_path("LDR", "comp"), 1672 "4x4x4", "-fast", "-zdim", "2"] 1673 1674 # Run the command, incrementally omitting arguments 1675 self.exec_with_omit(command, 7) 1676 1677 def test_tl_missing_args(self): 1678 """ 1679 Test -tl with missing arguments. 1680 """ 1681 # Build a valid command 1682 command = [ 1683 self.binary, "-tl", 1684 self.get_ref_image_path("LDR", "input", "A"), 1685 self.get_tmp_image_path("LDR", "decomp"), 1686 "4x4", "-fast"] 1687 1688 # Run the command, incrementally omitting arguments 1689 self.exec_with_omit(command, 2) 1690 1691 def test_tl_missing_input(self): 1692 """ 1693 Test -tl with a missing input file. 1694 """ 1695 # Build a valid command with a missing input file 1696 command = [ 1697 self.binary, "-tl", 1698 "./Test/Data/missing.png", 1699 self.get_tmp_image_path("LDR", "decomp"), 1700 "4x4", "-fast"] 1701 1702 self.exec(command) 1703 1704 def test_tl_unknown_input(self): 1705 """ 1706 Test -tl with an unknown input file extension. 1707 """ 1708 # Build an otherwise valid command with the test flaw 1709 command = [ 1710 self.binary, "-tl", 1711 "./Test/Data/empty.unk", 1712 self.get_tmp_image_path("LDR", "decomp"), 1713 "4x4", "-fast"] 1714 1715 self.exec(command) 1716 1717 def test_tl_missing_output(self): 1718 """ 1719 Test -tl with a missing output directory. 1720 """ 1721 # Build an otherwise valid command with the test flaw 1722 command = [ 1723 self.binary, "-tl", 1724 self.get_ref_image_path("LDR", "input", "A"), 1725 "./DoesNotExist/test.png", 1726 "4x4", "-fast"] 1727 1728 self.exec(command) 1729 1730 def test_tl_bad_block_size(self): 1731 """ 1732 Test -tl with an invalid block size. 1733 """ 1734 badSizes = [ 1735 "4x5", # Illegal 2D block size 1736 "3x3x4", # Illegal 3D block size 1737 "4x4x4x4", # Too many dimensions 1738 "4x", # Incomplete 2D block size 1739 "4x4x", # Incomplete 3D block size 1740 "4x4x4x", # Over-long 3D block size 1741 "4xe", # Illegal non-numeric character 1742 "4x4e" # Additional non-numeric character 1743 ] 1744 1745 # Build an otherwise valid command with the test flaw 1746 command = [ 1747 self.binary, "-tl", 1748 self.get_ref_image_path("LDR", "input", "A"), 1749 self.get_tmp_image_path("LDR", "decomp"), 1750 "4x4", "-fast"] 1751 1752 # Test that the underlying command is valid 1753 self.exec(command, True) 1754 1755 blockIndex = command.index("4x4") 1756 for badSize in badSizes: 1757 with self.subTest(blockSize=badSize): 1758 command[blockIndex] = badSize 1759 self.exec(command) 1760 1761 def test_tl_bad_preset(self): 1762 """ 1763 Test -tl with an invalid encoding preset. 1764 """ 1765 # Build an otherwise valid command with the test flaw 1766 command = [ 1767 self.binary, "-tl", 1768 self.get_ref_image_path("LDR", "input", "A"), 1769 self.get_tmp_image_path("LDR", "decomp"), 1770 "4x4", "-fastt"] 1771 1772 self.exec(command) 1773 1774 def test_tl_bad_argument(self): 1775 """ 1776 Test -tl with an unknown additional argument. 1777 """ 1778 # Build an otherwise valid command with the test flaw 1779 command = [ 1780 self.binary, "-tl", 1781 self.get_ref_image_path("LDR", "input", "A"), 1782 self.get_tmp_image_path("LDR", "decomp"), 1783 "4x4", "-fast", "-unknown"] 1784 1785 self.exec(command) 1786 1787 def test_dl_missing_args(self): 1788 """ 1789 Test -dl with missing arguments. 1790 """ 1791 # Build a valid command 1792 command = [ 1793 self.binary, "-dl", 1794 self.get_ref_image_path("LDR", "comp", "A"), 1795 self.get_tmp_image_path("LDR", "decomp")] 1796 1797 # Run the command, incrementally omitting arguments 1798 self.exec_with_omit(command, 2) 1799 1800 def test_dl_missing_output(self): 1801 """ 1802 Test -dl with a missing output directory. 1803 """ 1804 # Build an otherwise valid command with the test flaw 1805 command = [ 1806 self.binary, "-dl", 1807 self.get_ref_image_path("LDR", "comp", "A"), 1808 "./DoesNotExist/test.png"] 1809 1810 self.exec(command) 1811 1812 def test_cl_a_missing_args(self): 1813 """ 1814 Test -cl with -a and missing arguments. 1815 """ 1816 # Build a valid command 1817 command = [ 1818 self.binary, "-cl", 1819 self.get_ref_image_path("LDR", "input", "A"), 1820 self.get_tmp_image_path("LDR", "comp"), 1821 "4x4", "-fast", 1822 "-a", "2"] 1823 1824 # Run the command, incrementally omitting arguments 1825 self.exec_with_omit(command, 7) 1826 1827 def test_cl_cw_missing_args(self): 1828 """ 1829 Test -cl with -cw and missing arguments. 1830 """ 1831 # Build a valid command 1832 command = [ 1833 self.binary, "-cl", 1834 self.get_ref_image_path("LDR", "input", "A"), 1835 self.get_tmp_image_path("LDR", "comp"), 1836 "4x4", "-fast", 1837 "-cw", "0", "1", "2", "3"] 1838 1839 # Run the command, incrementally omitting arguments 1840 self.exec_with_omit(command, 7) 1841 1842 def test_cl_2partitionindexlimit_missing_args(self): 1843 """ 1844 Test -cl with -2partitionindexlimit and missing arguments. 1845 """ 1846 # Build a valid command 1847 command = [ 1848 self.binary, "-cl", 1849 self.get_ref_image_path("LDR", "input", "A"), 1850 self.get_tmp_image_path("LDR", "comp"), 1851 "4x4", "-fast", 1852 "-2partitionindexlimit", "3"] 1853 1854 # Run the command, incrementally omitting arguments 1855 self.exec_with_omit(command, 7) 1856 1857 def test_cl_3partitionindexlimit_missing_args(self): 1858 """ 1859 Test -cl with -3partitionindexlimit and missing arguments. 1860 """ 1861 # Build a valid command 1862 command = [ 1863 self.binary, "-cl", 1864 self.get_ref_image_path("LDR", "input", "A"), 1865 self.get_tmp_image_path("LDR", "comp"), 1866 "4x4", "-fast", 1867 "-3partitionindexlimit", "3"] 1868 1869 # Run the command, incrementally omitting arguments 1870 self.exec_with_omit(command, 7) 1871 1872 def test_cl_4partitionindexlimit_missing_args(self): 1873 """ 1874 Test -cl with -4partitionindexlimit and missing arguments. 1875 """ 1876 # Build a valid command 1877 command = [ 1878 self.binary, "-cl", 1879 self.get_ref_image_path("LDR", "input", "A"), 1880 self.get_tmp_image_path("LDR", "comp"), 1881 "4x4", "-fast", 1882 "-4partitionindexlimit", "3"] 1883 1884 # Run the command, incrementally omitting arguments 1885 self.exec_with_omit(command, 7) 1886 1887 def test_cl_2partitioncandidatelimit_missing_args(self): 1888 """ 1889 Test -cl with -2partitioncandidatelimit and missing arguments. 1890 """ 1891 # Build a valid command 1892 command = [ 1893 self.binary, "-cl", 1894 self.get_ref_image_path("LDR", "input", "A"), 1895 self.get_tmp_image_path("LDR", "comp"), 1896 "4x4", "-fast", 1897 "-2partitioncandidatelimit", "1"] 1898 1899 # Run the command, incrementally omitting arguments 1900 self.exec_with_omit(command, 7) 1901 1902 def test_cl_3partitioncandidatelimit_missing_args(self): 1903 """ 1904 Test -cl with -3partitioncandidatelimit and missing arguments. 1905 """ 1906 # Build a valid command 1907 command = [ 1908 self.binary, "-cl", 1909 self.get_ref_image_path("LDR", "input", "A"), 1910 self.get_tmp_image_path("LDR", "comp"), 1911 "4x4", "-fast", 1912 "-3partitioncandidatelimit", "3"] 1913 1914 # Run the command, incrementally omitting arguments 1915 self.exec_with_omit(command, 7) 1916 1917 1918 def test_cl_4partitioncandidatelimit_missing_args(self): 1919 """ 1920 Test -cl with -4partitioncandidatelimit and missing arguments. 1921 """ 1922 # Build a valid command 1923 command = [ 1924 self.binary, "-cl", 1925 self.get_ref_image_path("LDR", "input", "A"), 1926 self.get_tmp_image_path("LDR", "comp"), 1927 "4x4", "-fast", 1928 "-4partitioncandidatelimit", "3"] 1929 1930 # Run the command, incrementally omitting arguments 1931 self.exec_with_omit(command, 7) 1932 1933 def test_cl_blockmodelimit_missing_args(self): 1934 """ 1935 Test -cl with -blockmodelimit and missing arguments. 1936 """ 1937 # Build a valid command 1938 command = [ 1939 self.binary, "-cl", 1940 self.get_ref_image_path("LDR", "input", "A"), 1941 self.get_tmp_image_path("LDR", "comp"), 1942 "4x4", "-fast", 1943 "-blockmodelimit", "3"] 1944 1945 # Run the command, incrementally omitting arguments 1946 self.exec_with_omit(command, 7) 1947 1948 def test_cl_refinementlimit_missing_args(self): 1949 """ 1950 Test -cl with -refinementlimit and missing arguments. 1951 """ 1952 # Build a valid command 1953 command = [ 1954 self.binary, "-cl", 1955 self.get_ref_image_path("LDR", "input", "A"), 1956 self.get_tmp_image_path("LDR", "comp"), 1957 "4x4", "-fast", 1958 "-refinementlimit", "3"] 1959 1960 # Run the command, incrementally omitting arguments 1961 self.exec_with_omit(command, 7) 1962 1963 def test_cl_dblimit_missing_args(self): 1964 """ 1965 Test -cl with -dblimit and missing arguments. 1966 """ 1967 # Build a valid command 1968 command = [ 1969 self.binary, "-cl", 1970 self.get_ref_image_path("LDR", "input", "A"), 1971 self.get_tmp_image_path("LDR", "comp"), 1972 "4x4", "-fast", 1973 "-dblimit", "3"] 1974 1975 # Run the command, incrementally omitting arguments 1976 self.exec_with_omit(command, 7) 1977 1978 def test_cl_2partitionearlylimit_missing_args(self): 1979 """ 1980 Test -cl with -2partitionlimitfactor and missing arguments. 1981 """ 1982 # Build a valid command 1983 command = [ 1984 self.binary, "-cl", 1985 self.get_ref_image_path("LDR", "input", "A"), 1986 self.get_tmp_image_path("LDR", "comp"), 1987 "4x4", "-fast", 1988 "-2partitionlimitfactor", "3"] 1989 1990 # Run the command, incrementally omitting arguments 1991 self.exec_with_omit(command, 7) 1992 1993 def test_cl_3partitionearlylimit_missing_args(self): 1994 """ 1995 Test -cl with -3partitionlimitfactor and missing arguments. 1996 """ 1997 # Build a valid command 1998 command = [ 1999 self.binary, "-cl", 2000 self.get_ref_image_path("LDR", "input", "A"), 2001 self.get_tmp_image_path("LDR", "comp"), 2002 "4x4", "-fast", 2003 "-3partitionlimitfactor", "3"] 2004 2005 # Run the command, incrementally omitting arguments 2006 self.exec_with_omit(command, 7) 2007 2008 def test_cl_2planeearlylimit_missing_args(self): 2009 """ 2010 Test -cl with -2planelimitcorrelation and missing arguments. 2011 """ 2012 # Build a valid command 2013 command = [ 2014 self.binary, "-cl", 2015 self.get_ref_image_path("LDR", "input", "A"), 2016 self.get_tmp_image_path("LDR", "comp"), 2017 "4x4", "-fast", 2018 "-2planelimitcorrelation", "0.66"] 2019 2020 # Run the command, incrementally omitting arguments 2021 self.exec_with_omit(command, 7) 2022 2023 def test_cl_esw_missing_args(self): 2024 """ 2025 Test -cl with -esw and missing arguments. 2026 """ 2027 # Build a valid command 2028 command = [ 2029 self.binary, "-cl", 2030 self.get_ref_image_path("LDR", "input", "A"), 2031 self.get_tmp_image_path("LDR", "comp"), 2032 "4x4", "-fast", 2033 "-esw", "rgb1"] 2034 2035 # Run the command, incrementally omitting arguments 2036 self.exec_with_omit(command, 7) 2037 2038 def test_cl_esw_invalid_swizzle(self): 2039 """ 2040 Test -cl with -esw and invalid swizzles. 2041 """ 2042 badSwizzles = [ 2043 "", # Short swizzles 2044 "r", 2045 "rr", 2046 "rrr", 2047 "rrrrr", # Long swizzles 2048 ] 2049 2050 # Create swizzles with all invalid printable ascii codes 2051 good = ["r", "g", "b", "a", "0", "1"] 2052 for channel in string.printable: 2053 if channel not in good: 2054 badSwizzles.append(channel * 4) 2055 2056 # Build a valid base command 2057 command = [ 2058 self.binary, "-cl", 2059 self.get_ref_image_path("LDR", "input", "A"), 2060 self.get_tmp_image_path("LDR", "comp"), 2061 "4x4", "-fast", 2062 "-esw", "rgba"] 2063 2064 blockIndex = command.index("rgba") 2065 for badSwizzle in badSwizzles: 2066 with self.subTest(swizzle=badSwizzle): 2067 command[blockIndex] = badSwizzle 2068 self.exec(command) 2069 2070 def test_cl_ssw_missing_args(self): 2071 """ 2072 Test -cl with -ssw and missing arguments. 2073 """ 2074 # Build a valid command 2075 command = [ 2076 self.binary, "-cl", 2077 self.get_ref_image_path("LDR", "input", "A"), 2078 self.get_tmp_image_path("LDR", "comp"), 2079 "4x4", "-fast", 2080 "-ssw", "rgba"] 2081 2082 # Run the command, incrementally omitting arguments 2083 self.exec_with_omit(command, 7) 2084 2085 def test_cl_ssw_invalid_swizzle(self): 2086 """ 2087 Test -cl with -ssw and invalid swizzles. 2088 """ 2089 badSwizzles = [ 2090 "", # Short swizzles 2091 "rrrrr", # Long swizzles 2092 ] 2093 2094 # Create swizzles with all invalid printable ascii codes 2095 good = ["r", "g", "b", "a"] 2096 for channel in string.printable: 2097 if channel not in good: 2098 badSwizzles.append(channel * 4) 2099 2100 # Build a valid base command 2101 command = [ 2102 self.binary, "-cl", 2103 self.get_ref_image_path("LDR", "input", "A"), 2104 self.get_tmp_image_path("LDR", "comp"), 2105 "4x4", "-fast", 2106 "-ssw", "rgba"] 2107 2108 blockIndex = command.index("rgba") 2109 for badSwizzle in badSwizzles: 2110 with self.subTest(swizzle=badSwizzle): 2111 command[blockIndex] = badSwizzle 2112 self.exec(command) 2113 2114 def test_dl_dsw_missing_args(self): 2115 """ 2116 Test -dl with -dsw and missing arguments. 2117 """ 2118 # Build a valid command 2119 command = [ 2120 self.binary, "-dl", 2121 self.get_ref_image_path("LDR", "comp", "A"), 2122 self.get_tmp_image_path("LDR", "decomp"), 2123 "-dsw", "rgb1"] 2124 2125 # Run the command, incrementally omitting arguments 2126 self.exec_with_omit(command, 5) 2127 2128 def test_dl_dsw_invalid_swizzle(self): 2129 """ 2130 Test -dl with -dsw and invalid swizzles. 2131 """ 2132 badSwizzles = [ 2133 "", # Short swizzles 2134 "r", 2135 "rr", 2136 "rrr", 2137 "rrrrr", # Long swizzles 2138 ] 2139 2140 # Create swizzles with all invalid printable ascii codes 2141 good = ["r", "g", "b", "a", "z", "0", "1"] 2142 for channel in string.printable: 2143 if channel not in good: 2144 badSwizzles.append(channel * 4) 2145 2146 # Build a valid base command 2147 command = [ 2148 self.binary, "-dl", 2149 self.get_ref_image_path("LDR", "comp", "A"), 2150 self.get_tmp_image_path("LDR", "decomp"), 2151 "-dsw", "rgba"] 2152 2153 blockIndex = command.index("rgba") 2154 for badSwizzle in badSwizzles: 2155 with self.subTest(swizzle=badSwizzle): 2156 command[blockIndex] = badSwizzle 2157 self.exec(command) 2158 2159 def test_ch_mpsnr_missing_args(self): 2160 """ 2161 Test -ch with -mpsnr and missing arguments. 2162 """ 2163 # Build a valid command 2164 command = [ 2165 self.binary, "-ch", 2166 self.get_ref_image_path("HDR", "input", "A"), 2167 self.get_tmp_image_path("HDR", "comp"), 2168 "4x4", "-fast", 2169 "-mpsnr", "-5", "5"] 2170 2171 # Run the command, incrementally omitting arguments 2172 self.exec_with_omit(command, 7) 2173 2174 2175def main(): 2176 """ 2177 The main function. 2178 2179 Returns: 2180 int: The process return code. 2181 """ 2182 global g_TestEncoder 2183 2184 parser = argparse.ArgumentParser() 2185 2186 coders = ["none", "neon", "sse2", "sse4.1", "avx2"] 2187 parser.add_argument("--encoder", dest="encoder", default="avx2", 2188 choices=coders, help="test encoder variant") 2189 args = parser.parse_known_args() 2190 2191 # Set the encoder for this test run 2192 g_TestEncoder = args[0].encoder 2193 2194 # Set the sys.argv to remaining args (leave sys.argv[0] alone) 2195 sys.argv[1:] = args[1] 2196 2197 results = unittest.main(exit=False) 2198 return 0 if results.result.wasSuccessful() else 1 2199 2200 2201if __name__ == "__main__": 2202 sys.exit(main()) 2203