blob: 0a31502b0222c46e755a31019a74ac45d8bed997 [file] [edit]
# SPDX-License-Identifier: Apache-2.0
# -----------------------------------------------------------------------------
# Copyright 2020-2026 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.
# -----------------------------------------------------------------------------
'''
A TestSet is a single suite of related images used for testing, comprised of a
list of TestImage instances stored on the file system.
Images are stored on the file system in a structured layout that encodes
important metadata about each image, such as color profile and data encoding,
as part of the directory and file names used.
The directory hierarchy convention used is:
<set_name>/<color_profile>/<file_name>
The file name convention used is:
<color_profile>-<color_format>-<descriptive_name>[-<flags>].<extension>
'''
from pathlib import Path
from typing import Optional
class TestImageException(Exception):
'''
Exception thrown for bad test image specification.
'''
class TestSetException(Exception):
'''
Exception thrown for bad test set specification.
'''
class TestImage:
'''
Objects of this type contain metadata for a single test image on disk.
Attributes:
file_path: The path of the file on disk.
file_path_out: The path of the output file on disk.
test_format: The test format group.
file_name: The test file name.
color_profile: The image compression color profile.
color_format: The image color format.
name: The image human name.
is_3d: True if the image is 3D, else False.
is_alpha_scaled: True if the image wants alpha scaling, else False.
Class Attributes:
TEST_EXTS: Expected test image extensions.
PROFILES: Tuple of valid color profile values.
FORMATS: Tuple of valid color format values.
FLAGS: Map of valid flags (key) and their meaning (value).
'''
TEST_EXTS = ('.jpg', '.png', '.tga', '.dds', '.hdr', '.ktx')
PROFILES = ('ldr', 'ldrs', 'hdr')
FORMATS = ('l', 'la', 'xy', 'rgb', 'rgba')
FLAGS = {
'3': '3D image',
'a': 'Alpha scaled image'
}
def __init__(self, file_path: Path):
'''
Create a new image definition, based on a structured file path.
Args:
file_path: The path of the image on disk.
Raise:
TestImageException: The image is not found or path is malformed.
'''
self.file_path = file_path.resolve()
if not self.file_path.exists():
raise TestImageException(f'Image does not exist ({file_path})')
self.test_set = file_path.parent.parent.name
self.test_format = file_path.parent.name
self.file_name = file_path.name
# Decode the mandatory fields
self.color_profile = self._decode_color_profile(file_path.stem)
self.color_format = self._decode_color_format(file_path.stem)
self.name = self._decode_name(file_path.stem)
# Decode flags
flags = self._decode_flags(file_path.stem)
self.is_3d = '3' in flags
self.is_alpha_scaled = 'a' in flags
# Output file path, with file stored without extension
# TODO: Move this elsewhere as it assumes the directory structure
root_dir = Path(__file__).parent.parent
self.file_path_out = \
root_dir / 'TestOutput' / \
self.test_set / self.test_format / file_path.stem
def _decode_color_profile(self, file_name: str) -> str:
'''
Utility function to decode color profile from file name.
Args:
file_name: The base file name to parse.
Return:
Color profile.
Raise:
TestImageException: File path is malformed.
'''
parts = file_name.split('-')
if len(parts) < 3:
raise TestImageException(f'Malformed file name ({file_name})')
profile = parts[0]
if profile not in self.PROFILES:
raise TestImageException(f'Unknown color profile ({profile})')
return profile
def _decode_color_format(self, file_name: str) -> str:
'''
Utility function to decode color format from file name.
Args:
file_name: The base file name to parse.
Return:
Color format.
Raise:
TestImageException: File path is malformed.
'''
parts = file_name.split('-')
if len(parts) < 3:
raise TestImageException(f'Malformed file name ({self.file_name})')
cformat = parts[1]
if cformat not in self.FORMATS:
raise TestImageException(f'Unknown color format ({cformat})')
return cformat
def _decode_name(self, file_name: str) -> str:
'''
Utility function to decode human readable name from file name.
Args:
file_name: The base file name to parse.
Return:
Human readable name.
Raise:
TestImageException: File path is malformed.
'''
parts = file_name.split('-')
if len(parts) < 3:
raise TestImageException(f'Malformed file name ({self.file_name})')
return parts[2]
def _decode_flags(self, file_name: str) -> set[str]:
'''
Utility function to decode the flags from file name.
Args:
file_name: The base file name to parse.
Return:
Set of validated flags.
Raise:
TestImageException: File path is malformed.
'''
flags: set[str] = set()
parts = file_name.split('-')
# No flags specified
if len(parts) < 4:
return flags
# ... else decode the flags field
raw_flags = parts[3]
for flag in raw_flags:
if flag in flags:
raise TestImageException(f'Duplicate flag ({flag})')
if flag not in self.FLAGS:
raise TestImageException(f'Unknown flag ({flag})')
flags.add(flag)
return flags
class TestSet:
'''
A suite of related images used for testing, comprised of a list of
TestImage instances. TestImages are discovered by reading images from a
directory in the filesystem.
Attributes:
name: The name of the test set.
tests: The list of TestImages forming the set.
'''
def __init__(self, name: str, set_dir: Path, profiles: list[str],
formats: list[str], target_image: Optional[str] = None):
'''
Create a new TestSet using file-system discovery to find the images.
Args:
name: The name of the test set.
set_dir: The root directory of the test set.
profiles: The compressor profiles to allow.
formats: The image formats to allow.
target_image: The name of the image to keep (for bug repo).
Raise:
TestSetException: The test set could not be loaded.
'''
self.name = name
if not set_dir.exists() or not set_dir.is_dir():
raise TestSetException(f'Bad test set directory ({set_dir})')
self.tests = []
for (dir_path, _, file_names) in set_dir.walk():
for file_name in file_names:
file_path = dir_path / file_name
# Only select image files
ext = file_path.suffix
if ext not in TestImage.TEST_EXTS:
continue
# Create the TestImage for each file on disk
image = TestImage(file_path)
# Filter out the images we don't want to keep
if image.color_profile not in profiles:
continue
if image.color_format not in formats:
continue
if target_image and image.file_name != target_image:
continue
self.tests.append(image)
# Sort the images so they are in a stable order
self.tests.sort(key=lambda x: x.file_path)