mirror of
https://github.com/LTTLabsOSS/markbench-tests.git
synced 2026-01-08 05:33:52 -05:00
- adjusted artifact manager to not fail when copy file src and dest are the same - ffmpeg cpu directory fixes
190 lines
6.5 KiB
Python
190 lines
6.5 KiB
Python
"""Provides ArtifactManager class for capturing artifacts from test runs."""
|
|
|
|
import logging
|
|
import os
|
|
from collections.abc import Callable
|
|
from dataclasses import dataclass
|
|
from enum import Enum, unique
|
|
from pathlib import Path
|
|
from shutil import copy
|
|
|
|
import cv2
|
|
import dxcam
|
|
import mss
|
|
import numpy as np
|
|
import yaml
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@unique
|
|
class ArtifactType(Enum):
|
|
"""
|
|
Describes different types of artifacts to be saved from test runs.
|
|
"""
|
|
|
|
CONFIG_IMAGE = "config_image"
|
|
"""
|
|
Meant to describe images displaying an applications settings.
|
|
For games this might be a screenshot of the in-game settings menu.
|
|
"""
|
|
|
|
RESULTS_IMAGE = "results_image"
|
|
"""
|
|
Meant to describe images displaying results of a benchmark.
|
|
For games with built in benchmarks, this might be a screenshot of an in-game benchmark results screen.
|
|
"""
|
|
|
|
CONFIG_TEXT = "config_text"
|
|
"""
|
|
Meant to describe text-based files which contain application settings.
|
|
For games this might be a .ini or .cfg file containing graphics settings.
|
|
"""
|
|
|
|
RESULTS_TEXT = "results_text"
|
|
"""
|
|
Meant to describe text-based files which contain results of a benchmark.
|
|
For games with built in benchmarks, this might be a .txt or .xml file containing results from an in-game benchmark.
|
|
"""
|
|
|
|
|
|
_IMAGE_ARTIFACT_TYPES = (ArtifactType.CONFIG_IMAGE, ArtifactType.RESULTS_IMAGE)
|
|
|
|
|
|
@dataclass
|
|
class Artifact:
|
|
"""
|
|
Describes an artifact captured by the ArtifactManager.
|
|
"""
|
|
|
|
filename: str
|
|
type: ArtifactType
|
|
description: str
|
|
|
|
def as_dict(self) -> dict:
|
|
"""
|
|
Returns the Artifact object as a dictionary. Converts its type to a string value.
|
|
"""
|
|
d = self.__dict__.copy()
|
|
d["type"] = d["type"].value
|
|
return d
|
|
|
|
|
|
class ArtifactManager:
|
|
"""
|
|
Used to manage artifacts captured during a test run, either by coping files or taking screenshots.
|
|
The manager maintains a list of artifacts it has captured and can produce a manifest file listing them.
|
|
"""
|
|
|
|
def __init__(self, output_path: str | os.PathLike) -> None:
|
|
self.output_path = Path(output_path)
|
|
self.artifacts: list[Artifact] = []
|
|
|
|
self.output_path.mkdir(parents=True, exist_ok=True)
|
|
|
|
def copy_file(
|
|
self, src: str | os.PathLike, artifact_type: ArtifactType, description=""
|
|
):
|
|
"""
|
|
Copies a file from `src` to the manager's `output_path` and adds a new Artifact to the manager's artifacts list.
|
|
|
|
The newly created artifact's `type` and `description` fields are set to the given
|
|
`artifact_type` and `description` arguments respectively while the artifact's `filename`
|
|
is set to the basename of `src`.
|
|
|
|
Raises a `ValueError` if `src` points to a directory instead of a file.
|
|
"""
|
|
src_path = Path(src)
|
|
if src_path.is_dir():
|
|
raise ValueError("src should point to a file, not a directory")
|
|
filename = src_path.name
|
|
try:
|
|
if Path(src).resolve() != Path(self.output_path).resolve() / filename:
|
|
logger.info("Copying %s to %s", src, self.output_path / filename)
|
|
copy(src, self.output_path / filename)
|
|
else:
|
|
logger.info(
|
|
"Skipping copy of %s to %s as it already exists",
|
|
src,
|
|
self.output_path / filename,
|
|
)
|
|
artifact = Artifact(filename, artifact_type, description)
|
|
self.artifacts.append(artifact)
|
|
except OSError as e:
|
|
raise e
|
|
|
|
def take_screenshot(
|
|
self,
|
|
filename: str,
|
|
artifact_type: ArtifactType,
|
|
description="",
|
|
screenshot_override: Callable[[str | os.PathLike], None] | None = None,
|
|
):
|
|
"""
|
|
Takes a screenshot and saves it to the manager's `output_path` with the given `filename`
|
|
and adds a new Artifact to the manager's artifact list.
|
|
|
|
The newly created artifact's `filename`, `type` and `description` fields are set to the
|
|
given `filename`, `artifact_type` and `description` arguments respectively.
|
|
|
|
Raises a `ValueError` if `artifact_type` is not one of the `ArtifactType` values which represents an image.
|
|
"""
|
|
if artifact_type not in _IMAGE_ARTIFACT_TYPES:
|
|
raise ValueError(
|
|
"artifact_type should be a type that represents an image artifact"
|
|
)
|
|
|
|
if screenshot_override is None:
|
|
with mss.mss() as sct:
|
|
sct.shot(output=str(self.output_path / filename))
|
|
else:
|
|
screenshot_override(self.output_path / filename)
|
|
artifact = Artifact(filename, artifact_type, description)
|
|
self.artifacts.append(artifact)
|
|
|
|
def take_screenshot_vulkan(
|
|
self,
|
|
filename: str,
|
|
artifact_type: ArtifactType,
|
|
description="",
|
|
screenshot_override: Callable[[str | os.PathLike], None] | None = None,
|
|
):
|
|
"""
|
|
Takes a screenshot using dxcam and saves it to the manager's `output_path` with the given `filename`.
|
|
Adds a new Artifact to the manager's artifact list.
|
|
|
|
Raises a `ValueError` if `artifact_type` is not one of the `_IMAGE_ARTIFACT_TYPES` values which represent an image.
|
|
"""
|
|
if artifact_type not in _IMAGE_ARTIFACT_TYPES:
|
|
raise ValueError(
|
|
"artifact_type should be a type that represents an image artifact"
|
|
)
|
|
|
|
output_filepath = str(self.output_path / filename)
|
|
|
|
if screenshot_override is None:
|
|
camera = dxcam.create(output_idx=0) # Initialize DXCam
|
|
frame = camera.grab() # Capture the screenshot
|
|
|
|
if frame is None:
|
|
raise RuntimeError("Failed to capture screenshot with dxcam.")
|
|
|
|
# Convert to BGR format (since dxcam outputs in RGB)
|
|
frame_bgr = cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR)
|
|
|
|
# Save image using OpenCV
|
|
cv2.imwrite(output_filepath, frame_bgr)
|
|
else:
|
|
screenshot_override(output_filepath)
|
|
|
|
artifact = Artifact(filename, artifact_type, description)
|
|
self.artifacts.append(artifact)
|
|
|
|
def create_manifest(self):
|
|
"""
|
|
Creates a file `artifacts.yaml` which lists the artifacts in the manager's `artifacts` list.
|
|
The file is created at the location specified by the manager's `output_path`.
|
|
"""
|
|
with open(self.output_path / "artifacts.yaml", encoding="utf-8", mode="w") as f:
|
|
yaml.safe_dump([a.as_dict() for a in self.artifacts], f, sort_keys=False)
|