# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Unittests for overlay_support.py"""

import json
import os
import unittest
from unittest import mock

# vpython-provided modules.
from pyfakefs import fake_filesystem_unittest  # pylint:disable=import-error

from gpu_tests import constants
from gpu_tests import overlay_support

rotation = overlay_support.VideoRotation

# pylint: disable=too-many-public-methods


class PresentationModeEventToStrUnittest(unittest.TestCase):
  def testAllEventsHaveStringEnum(self):
    """Tests that all defined enums have corresponding string values."""
    for event in overlay_support.PresentationModeEvent:
      event_str = overlay_support.PresentationModeEventToStr(event)
      self.assertNotIn('(unknown)', event_str)

  def testIntConversion(self):
    """Tests that ints are automatically converted to enum values."""
    event_str = overlay_support.PresentationModeEventToStr(0)
    self.assertNotIn('(unknown)', event_str)

  def testUnknownValue(self):
    """Tests behavior when an unknown value is provided."""
    event_str = overlay_support.PresentationModeEventToStr(999)
    self.assertEqual(event_str, '999 (unknown)')


class GpuOverlayConfigUnittest(unittest.TestCase):
  def testNoDirectCompositionSupport(self):
    """Tests behavior when no direct composition support is specified."""
    config = overlay_support.GpuOverlayConfig().OnDriverVersion('1')

    self.assertFalse(config.direct_composition)
    self.assertFalse(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.NONE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.NONE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.NONE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.BGRA8
      )
      self.assertEqual(
        config.GetExpectedPixelFormat(pixel_format),
        overlay_support.PixelFormat.BGRA8,
      )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.COMPOSED,
    )

  def testSoftwareDirectCompositionSupport(self):
    """Tests behavior when software direct composition support is specified."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.BGRA8
      )
      self.assertEqual(
        config.GetExpectedPixelFormat(pixel_format),
        overlay_support.PixelFormat.BGRA8,
      )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testNV12HardwareDirectCompositionSupport(self):
    """Tests behavior when NV12 direct composition support is specified."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SCALING
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertTrue(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.NV12
      )
      if pixel_format == overlay_support.PixelFormat.NV12:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.NV12,
        )
      else:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.BGRA8,
        )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.NV12, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testYUY2HardwareDirectCompositionSupport(self):
    """Tests behavior when YUY2 direct composition support is specified."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareYUY2Support()
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SCALING
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertTrue(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.YUY2
      )
      if pixel_format == overlay_support.PixelFormat.YUY2:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.YUY2,
        )
      else:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.BGRA8,
        )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.YUY2, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testBGRA8HardwareDirectCompositionSupport(self):
    """Tests behavior when BGRA8 direct composition support is specified."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareBGRA8Support()
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SCALING
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertTrue(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.BGRA8
      )
      self.assertEqual(
        config.GetExpectedPixelFormat(pixel_format),
        overlay_support.PixelFormat.BGRA8,
      )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testForceComposedBGRA8(self):
    """Tests behavior when software BGRA8 overlays are forcibly composed."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithForceComposedBGRA8(
        driver_conditionals=[overlay_support.DriverConditional('lt', '21.0.0')]
      )
      .OnDriverVersion('20.0.3')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.BGRA8
      )
      self.assertEqual(
        config.GetExpectedPixelFormat(pixel_format),
        overlay_support.PixelFormat.BGRA8,
      )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.COMPOSED,
    )

  def testInvalidDriver(self):
    """Tests behavior when an invalid driver is used."""
    # This should end up being equivalent to only having software support.
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support(
        driver_conditionals=[overlay_support.DriverConditional('ge', '21.0.0')]
      )
      .WithHardwareYUY2Support(
        driver_conditionals=[overlay_support.DriverConditional('ge', '21.0.0')]
      )
      .OnDriverVersion('20.0.3')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.BGRA8
      )
      self.assertEqual(
        config.GetExpectedPixelFormat(pixel_format),
        overlay_support.PixelFormat.BGRA8,
      )

  def testZeroCopyVideoRotation(self):
    """Tests the effect of video rotation on zero copy."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supported_codecs=[overlay_support.ZeroCopyCodec.VP9]
        )
      )
      .OnDriverVersion('1')
    )

    for r in overlay_support.VideoRotation:
      zero_copy = config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=r,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
      if r == rotation.UNROTATED:
        self.assertTrue(zero_copy)
      else:
        self.assertFalse(zero_copy)

  def testZeroCopyPixelFormat(self):
    """Tests the effect of pixel format on zero copy."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supported_codecs=[overlay_support.ZeroCopyCodec.VP9]
        )
      )
      .OnDriverVersion('1')
    )

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      zero_copy = config.GetExpectedZeroCopyUsage(
        expected_pixel_format=pixel_format,
        video_rotation=rotation.UNROTATED,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
      if pixel_format == overlay_support.PixelFormat.NV12:
        self.assertTrue(zero_copy)
      else:
        self.assertFalse(zero_copy)

  def testZeroCopyScaledVideo(self):
    """Tests the effect of scaled video support on zero copy."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supported_codecs=[overlay_support.ZeroCopyCodec.VP9]
        )
      )
      .OnDriverVersion('1')
    )

    self.assertTrue(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
    )

    # Same as above but with fullsize=False.
    self.assertFalse(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=False,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
    )

    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supports_scaled_video=True,
          supported_codecs=[overlay_support.ZeroCopyCodec.VP9],
        )
      )
      .OnDriverVersion('1')
    )

    # Same as first assert.
    self.assertTrue(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
    )

    # Same as second assert, but now we expect True.
    self.assertTrue(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=False,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
    )

  def testZeroCopyCodecSupport(self):
    """Tests the effect of codec support on zero copy."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supported_codecs=[overlay_support.ZeroCopyCodec.H264]
        )
      )
      .OnDriverVersion('1')
    )

    self.assertTrue(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.H264,
      )
    )

    self.assertFalse(
      config.GetExpectedZeroCopyUsage(
        expected_pixel_format=overlay_support.PixelFormat.NV12,
        video_rotation=rotation.UNROTATED,
        fullsize=True,
        codec=overlay_support.ZeroCopyCodec.VP9,
      )
    )

  def testNV12RotatedVideo(self):
    """Tests NV12 behavior with rotated video."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support(supported_rotations=[rotation.ROT180])
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SCALING
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertTrue(config.supports_hw_nv12_overlays)
    self.assertFalse(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.NV12
      )
      if pixel_format == overlay_support.PixelFormat.NV12:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.NV12,
        )
      else:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.BGRA8,
        )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.NV12, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.NV12, rotation.ROT90
      ),
      overlay_support.PresentationMode.COMPOSED,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.NV12, rotation.ROT180
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.NV12, rotation.ROT270
      ),
      overlay_support.PresentationMode.COMPOSED,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testYUY2RotatedVideo(self):
    """Tests YUY2 behavior with rotated video."""
    config = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareYUY2Support(supported_rotations=[rotation.ROT180])
      .OnDriverVersion('1')
    )

    self.assertTrue(config.direct_composition)
    self.assertTrue(config.supports_overlays)

    self.assertEqual(
      config.nv12_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )
    self.assertEqual(
      config.yuy2_overlay_support, overlay_support.OverlaySupport.SCALING
    )
    self.assertEqual(
      config.bgra8_overlay_support, overlay_support.OverlaySupport.SOFTWARE
    )

    self.assertFalse(config.supports_hw_nv12_overlays)
    self.assertTrue(config.supports_hw_yuy2_overlays)
    self.assertFalse(config.supports_hw_bgra8_overlays)

    for pixel_format in overlay_support.PixelFormat.ALL_PIXEL_FORMATS:
      self.assertEqual(
        config.GetExpectedPixelFormat(None), overlay_support.PixelFormat.YUY2
      )
      if pixel_format == overlay_support.PixelFormat.YUY2:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.YUY2,
        )
      else:
        self.assertEqual(
          config.GetExpectedPixelFormat(pixel_format),
          overlay_support.PixelFormat.BGRA8,
        )

    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.YUY2, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.YUY2, rotation.ROT90
      ),
      overlay_support.PresentationMode.COMPOSED,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.YUY2, rotation.ROT180
      ),
      overlay_support.PresentationMode.OVERLAY,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.YUY2, rotation.ROT270
      ),
      overlay_support.PresentationMode.COMPOSED,
    )
    self.assertEqual(
      config.GetExpectedPresentationMode(
        overlay_support.PixelFormat.BGRA8, rotation.UNROTATED
      ),
      overlay_support.PresentationMode.OVERLAY,
    )

  def testDriverVersionRequired(self):
    """Tests that usage fails if a driver version is not added."""
    config = overlay_support.GpuOverlayConfig()

    with self.assertRaises(AssertionError):
      _ = config.nv12_overlay_support

  def testDriverCanBeSetWithSameValue(self):  # pylint: disable=no-self-use
    """Tests that the driver version can be set again with the same value."""
    config = (
      overlay_support.GpuOverlayConfig()
      .OnDriverVersion('1')
      .OnDriverVersion('1')
    )
    del config

  def testDriverCannotBeSetWithDifferentValue(self):
    """Tests that the driver version cannot be set with a different value."""
    config = overlay_support.GpuOverlayConfig().OnDriverVersion('1')
    with self.assertRaises(AssertionError):
      config.OnDriverVersion('2')

  def testUnknownPixelFormat(self):
    """Tests behavior when an unknown pixel format is specified."""
    config = overlay_support.GpuOverlayConfig().OnDriverVersion('1')
    with self.assertRaises(AssertionError):
      config.GetExpectedPixelFormat('NotReal')

    with self.assertRaises(KeyError):
      config.GetExpectedPresentationMode('NotReal', rotation.UNROTATED)

  def testEqualityDifferentClass(self):
    """Tests __eq__ behavior between GpuOverlayConfig and other types."""
    config = overlay_support.GpuOverlayConfig()

    self.assertNotEqual(config, 'NotAnOverlayConfig')

  def testEqualityBase(self):
    """Tests __eq__ behavior between GpuOverlayConfigs without arguments."""
    config = overlay_support.GpuOverlayConfig()
    other = overlay_support.GpuOverlayConfig()
    self.assertEqual(config, other)

    other.WithDirectComposition()
    self.assertNotEqual(config, other)
    config.WithDirectComposition()
    self.assertEqual(config, other)

    other.WithHardwareNV12Support()
    self.assertNotEqual(config, other)
    config.WithHardwareNV12Support()
    self.assertEqual(config, other)

    other.WithHardwareYUY2Support()
    self.assertNotEqual(config, other)
    config.WithHardwareYUY2Support()
    self.assertEqual(config, other)

    other.WithHardwareBGRA8Support()
    self.assertNotEqual(config, other)
    config.WithHardwareBGRA8Support()
    self.assertEqual(config, other)

  def testEqualityDriverConditionals(self):
    """Tests __eq__ behavior between GpuOverlayConfigs w/ DriverConditionals."""
    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    self.assertNotEqual(config, other)
    config.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    config.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('le', '1.0.0')]
    )
    self.assertNotEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    config.WithHardwareNV12Support(
      driver_conditionals=[overlay_support.DriverConditional('ge', '2.0.0')]
    )
    self.assertNotEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithForceComposedBGRA8(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    self.assertNotEqual(config, other)
    config.WithForceComposedBGRA8(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithForceComposedBGRA8(
      driver_conditionals=[overlay_support.DriverConditional('ge', '1.0.0')]
    )
    config.WithForceComposedBGRA8(
      driver_conditionals=[overlay_support.DriverConditional('le', '1.0.0')]
    )
    self.assertNotEqual(config, other)

  def testEqualitySupportedRotations(self):
    """Tests __eq__ behavior between GpuOverlayConfigs w/ supported rotations"""
    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithHardwareNV12Support(supported_rotations=[rotation.ROT180])
    self.assertNotEqual(config, other)
    config.WithHardwareNV12Support(supported_rotations=[rotation.ROT180])
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithHardwareNV12Support(supported_rotations=[rotation.ROT180])
    config.WithHardwareNV12Support(supported_rotations=[rotation.ROT90])
    self.assertNotEqual(config, other)

  def testEqualityZeroCopy(self):
    """Tests __eq__ behavior between GpuOverlayConfigs w/ zero copy configs."""
    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithZeroCopyConfig(overlay_support.ZeroCopyConfig())
    config.WithZeroCopyConfig(overlay_support.ZeroCopyConfig())
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithZeroCopyConfig(
      overlay_support.ZeroCopyConfig(supports_scaled_video=True)
    )
    self.assertNotEqual(config, other)
    config.WithZeroCopyConfig(
      overlay_support.ZeroCopyConfig(supports_scaled_video=True)
    )
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.WithZeroCopyConfig(
      overlay_support.ZeroCopyConfig(
        supported_codecs=[overlay_support.ZeroCopyCodec.H264]
      )
    )
    self.assertNotEqual(config, other)
    config.WithZeroCopyConfig(
      overlay_support.ZeroCopyConfig(
        supported_codecs=[overlay_support.ZeroCopyCodec.H264]
      )
    )
    self.assertEqual(config, other)

  def testEqualityDriverVersion(self):
    """Tests __eq__ behavior between GpuOverlayConfigs w/ driver version."""
    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.OnDriverVersion('1')
    self.assertNotEqual(config, other)
    config.OnDriverVersion('1')
    self.assertEqual(config, other)

    config = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other = overlay_support.GpuOverlayConfig().WithDirectComposition()
    other.OnDriverVersion('1')
    config.OnDriverVersion('2')
    self.assertNotEqual(config, other)


def _createMockGpu(vendor: constants.GpuVendor | int, device: int) -> mock.Mock:
  gpu = mock.Mock()
  gpu.vendor_id = vendor
  gpu.device_id = device
  gpu.driver_version = '1.0.0'
  return gpu


class GetOverlayConfigForGpuUnittest(unittest.TestCase):
  def testKnownGpu(self):  # pylint: disable=no-self-use
    """Tests behavior when a known GPU is provided."""
    gpu = _createMockGpu(constants.GpuVendor.INTEL, 0x3E92)

    overlay_support.GetOverlayConfigForGpu(gpu)

  def testUnknownVendor(self):
    """Tests behavior when an unknown GPU vendor is provided."""
    gpu = _createMockGpu(0x1234, 0x3E92)

    with self.assertRaisesRegex(
      RuntimeError,
      'GPU with vendor ID 0x1234 and device ID 0x3e92 does not have an '
      'overlay config specified',
    ):
      overlay_support.GetOverlayConfigForGpu(gpu)

  def testUnknownDevice(self):
    """Tests behavior when an unknown GPU devices is provided."""
    gpu = _createMockGpu(constants.GpuVendor.INTEL, 0x1234)

    with self.assertRaisesRegex(
      RuntimeError,
      'GPU with vendor ID 0x8086 and device ID 0x1234 does not have an '
      'overlay config specified',
    ):
      overlay_support.GetOverlayConfigForGpu(gpu)


# mock.patch.dict is used for all tests here to ensure that any OVERLAY_CONFIGS
# changes do not persist across tests.
class ParseOverlayJsonFileUnittest(fake_filesystem_unittest.TestCase):
  def setUp(self):
    self.setUpPyfakefs()
    os.makedirs('tmp')
    self.filepath = os.path.join('tmp', 'input.json')

  def setJson(self, json_content: dict) -> None:
    with open(self.filepath, 'w', encoding='utf-8') as outfile:
      json.dump(json_content, outfile)

  @mock.patch.dict(
    overlay_support.OVERLAY_CONFIGS, overlay_support.OVERLAY_CONFIGS, clear=True
  )
  def testDuplicateConfig(self):
    """Tests behavior when a duplicate config is provided."""
    json_content = {
      '0x8086': {
        '0x3e92': [],
      }
    }
    self.setJson(json_content)
    gpu = _createMockGpu(vendor=constants.GpuVendor.INTEL, device=0x3E92)
    original_config = overlay_support.GetOverlayConfigForGpu(gpu)
    overlay_support.ParseOverlayJsonFile(self.filepath)
    updated_config = overlay_support.GetOverlayConfigForGpu(gpu)
    self.assertEqual(updated_config, original_config)

  @mock.patch.dict(
    overlay_support.OVERLAY_CONFIGS, overlay_support.OVERLAY_CONFIGS, clear=True
  )
  def testUnknownVendor(self):
    """Tests behavior when an unknown vendor is provided."""
    json_content = {
      '0x1234': {
        '0x3e92': [],
      },
    }
    self.setJson(json_content)
    with self.assertRaises(ValueError):
      overlay_support.ParseOverlayJsonFile(self.filepath)

  @mock.patch.dict(
    overlay_support.OVERLAY_CONFIGS, overlay_support.OVERLAY_CONFIGS, clear=True
  )
  def testNonHexVendor(self):
    """Tests behavior when a non-hexadecimal vendor ID is provided."""
    json_content = {
      '8086': {
        '0x3e92': [],
      },
    }
    self.setJson(json_content)
    with self.assertRaises(AssertionError):
      overlay_support.ParseOverlayJsonFile(self.filepath)

  @mock.patch.dict(
    overlay_support.OVERLAY_CONFIGS, overlay_support.OVERLAY_CONFIGS, clear=True
  )
  def testNonHexDevice(self):
    """Tests behavior when a non-hexadecimal device ID is provided."""
    json_content = {
      '0x8086': {
        '3e92': [],
      },
    }
    self.setJson(json_content)
    with self.assertRaises(AssertionError):
      overlay_support.ParseOverlayJsonFile(self.filepath)

  def _parseTestHelper(self, json_content, expected):
    self.setJson(json_content)
    overlay_support.ParseOverlayJsonFile(self.filepath)
    actual = overlay_support.GetOverlayConfigForGpu(
      _createMockGpu(constants.GpuVendor.INTEL, 0x1234)
    )
    self.assertEqual(actual, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testBaseConfig(self):
    """Tests behavior when no additional functions are specified."""
    json_content = {
      '0x8086': {
        '0x1234': [],
      },
    }
    expected = overlay_support.GpuOverlayConfig().OnDriverVersion('1.0.0')
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithDirectComposition(self):
    """Tests behavior when direct composition is specified."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithHardwareNV12Support(self):
    """Tests behavior when NV12 support is specified."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithHardwareNV12Support',
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support()
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithHardwareYUY2Support(self):
    """Tests behavior when YUY2 support is specified."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithHardwareYUY2Support',
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareYUY2Support()
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithHardwareBGRA8Support(self):
    """Tests behavior when BGRA8 support is specified."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithHardwareBGRA8Support',
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareBGRA8Support()
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithHardwareSupportWithDriverConditional(self):
    """Tests behavior when support is specified with driver conditionals"""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithHardwareNV12Support',
            'args': {
              'driver_conditionals': [
                ['ge', '2.0.0'],
              ],
            },
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support(
        driver_conditionals=[overlay_support.DriverConditional('ge', '2.0.0')]
      )
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithHardwareSupportWithSupportedRotation(self):
    """Tests behavior when support is specified with supported rotations."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithHardwareNV12Support',
            'args': {
              'supported_rotations': [
                180,
              ],
            },
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithHardwareNV12Support(supported_rotations=[rotation.ROT180])
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithForceComposedBGRA8(self):
    """Tests behavior when composed BGRA8 is forced."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithForceComposedBGRA8',
            'args': {
              'driver_conditionals': [
                ['ge', '2.0.0'],
              ],
            },
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithForceComposedBGRA8(
        driver_conditionals=[overlay_support.DriverConditional('ge', '2.0.0')]
      )
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)

  @mock.patch.dict(overlay_support.OVERLAY_CONFIGS, {}, clear=True)
  def testWithZeroCopyConfig(self):
    """Tests behavior when a zero copy config is specified."""
    json_content = {
      '0x8086': {
        '0x1234': [
          {
            'function': 'WithDirectComposition',
          },
          {
            'function': 'WithZeroCopyConfig',
            'args': {
              'supports_scaled_video': True,
              'supported_codecs': [
                'H264',
              ],
            },
          },
        ],
      },
    }
    expected = (
      overlay_support.GpuOverlayConfig()
      .WithDirectComposition()
      .WithZeroCopyConfig(
        overlay_support.ZeroCopyConfig(
          supports_scaled_video=True,
          supported_codecs=[overlay_support.ZeroCopyCodec.H264],
        )
      )
      .OnDriverVersion('1.0.0')
    )
    self._parseTestHelper(json_content, expected)


if __name__ == '__main__':
  unittest.main(verbosity=2)
