import os
import random
import torch
import numpy as np
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.metrics import precision_score, recall_score
from torch.nn import functional as F
from PIL import Image, ImageDraw, ImageFont
import matplotlib.pyplot as plt
import seaborn as sns
from colpali_engine.interpretability import (
    get_similarity_maps_from_embeddings,
    plot_all_similarity_maps,
)

# Path to extracted Flickr8k dataset
FLICKR8K_IMAGES_PATH = "flickr8k/Images"
FLICKR8K_CAPTIONS_PATH = "flickr8k/captions.txt"

# Function to load image-text pairs from Flickr8k
def load_flickr8k_data(images_path, captions_path, fraction=0.1):
    # Read captions file
    with open(captions_path, "r") as f:
        captions_data = f.readlines()[1:]  # Skip header

    # Parse captions
    image_text_pairs = {}
    for line in captions_data:
        image_name, caption = line.strip().split(",", 1)
        if image_name not in image_text_pairs:
            image_text_pairs[image_name] = []
        image_text_pairs[image_name].append(caption)

    # Load only a fraction of the dataset
    selected_images = random.sample(list(image_text_pairs.keys()), int(len(image_text_pairs) * fraction))
    image_text_pairs = {k: image_text_pairs[k] for k in selected_images}

    # Create pairs of images and captions
    pairs = []
    for image_name, captions in image_text_pairs.items():
        image_path = os.path.join(images_path, image_name)
        if os.path.exists(image_path):
            pairs.append((Image.open(image_path), random.choice(captions)))
    return pairs

# Function to create unrelated pairs
def create_unrelated_pairs(image_text_pairs):
    """
    Creates unrelated pairs of images and texts by randomly shuffling the texts.

    Args:
        image_text_pairs (list): A list of tuples containing images and their corresponding texts.

    Returns:
        list: A list of tuples containing images and unrelated texts.
    """
    images, texts = zip(*image_text_pairs)
    unrelated_texts = random.sample(texts, len(texts))
    return list(zip(images, unrelated_texts))

def create_visual_pairs(image_text_pairs):
    """
    Creates pairs of original and augmented images from image-text pairs.

    This function takes a list of image-text pairs and creates new pairs consisting
    of the original images and their augmented versions. The augmentation used
    in this implementation is a horizontal flip.

    Args:
        image_text_pairs (list): A list of tuples containing (image, text) pairs,
            where images are PIL Image objects and texts are strings.

    Returns:
        list: A list of tuples containing (original_image, augmented_image) pairs,
            where both elements are PIL Image objects.
    """
    from torchvision.transforms import ToTensor
    images, _ = zip(*image_text_pairs)
    augmented_images = [ToTensor()(image).flip(-1) for image in images]  # Example augmentation: horizontal flip
    return list(zip(images, augmented_images))

def get_embeddings(images, texts, model_id="google/siglip-base-patch16-224"):
    """
    Given lists of images and texts, returns normalized embeddings for both.
    """
    # Ensure texts is a list of strings
    if not all(isinstance(t, str) for t in texts):
        raise ValueError("All text inputs must be strings.")

    device = "cuda" if torch.cuda.is_available() else "cpu"
    model = AutoModel.from_pretrained(model_id, ignore_mismatched_sizes=True).to(device)
    processor = AutoProcessor.from_pretrained(model_id)

    # Preprocess images and texts
    image_inputs = processor(images=images, return_tensors="pt").to(device)
    text_inputs = processor(text=texts, return_tensors="pt", padding="max_length").to(device)

    with torch.no_grad():
        image_embeds = model.get_image_features(**image_inputs)
        text_embeds = model.get_text_features(**text_inputs)

    # Normalize embeddings
    image_embeds = image_embeds / image_embeds.norm(dim=-1, keepdim=True)
    text_embeds = text_embeds / text_embeds.norm(dim=-1, keepdim=True)

    return image_embeds, text_embeds

def cosine_similarity_analysis(embeddings1, embeddings2, title):
    """
    Computes cosine similarity for matching and unrelated pairs and compares distributions.
    """
    similarities = cosine_similarity(embeddings1.cpu().numpy(), embeddings2.cpu().numpy())

    # Matching pairs: Diagonal of the similarity matrix
    matching_similarities = np.diag(similarities)

    # Unrelated pairs: Off-diagonal similarities
    unrelated_similarities = similarities[~np.eye(similarities.shape[0], dtype=bool)]

    print(f"### {title} ###")
    print(f"Mean Matching Similarity: {np.mean(matching_similarities):.4f}")
    print(f"Mean Unrelated Similarity: {np.mean(unrelated_similarities):.4f}")
    print()

    # Plot distributions
    plt.figure(figsize=(10, 6))
    sns.histplot(matching_similarities, kde=True, label="Matching Pairs", color="blue", bins=30)
    sns.histplot(unrelated_similarities, kde=True, label="Unrelated Pairs", color="red", bins=30)
    plt.title(f"{title}: Cosine Similarity Distributions")
    plt.xlabel("Cosine Similarity")
    plt.ylabel("Frequency")
    plt.legend()
    plt.show()

### b. Nearest-Neighbor Retrieval
def retrieval_metrics(query_embeds, target_embeds, ground_truth_indices, k=5):
    """
    Computes Precision@k and Recall@k for nearest-neighbor retrieval.

    This function evaluates the effectiveness of retrieval by calculating Precision@k and Recall@k.
    Precision@k measures the accuracy of the top-k retrieved items, while Recall@k measures the ability
    to find the relevant item within the top-k retrieved items.  It assumes there's only one true
    match per query.

    Args:
        query_embeds (torch.Tensor): Embeddings of the query data.
        target_embeds (torch.Tensor): Embeddings of the target data (database).
        ground_truth_indices (list): List of indices in the target data representing the true matches for each query.
        k (int): The number of top results to consider.

    Returns:
        tuple: A tuple containing mean Precision@k and mean Recall@k.
    """
    similarities = cosine_similarity(query_embeds.cpu().numpy(), target_embeds.cpu().numpy())
    sorted_indices = np.argsort(-similarities, axis=1)[:, :k]  # Top-k indices

    # Compute metrics
    precisions = []
    recalls = []
    for i, true_idx in enumerate(ground_truth_indices):
        retrieved_indices = sorted_indices[i]
        true_positives = int(true_idx in retrieved_indices)
        precisions.append(true_positives / k)
        recalls.append(true_positives / 1)  # Only one true match per query

    mean_precision = np.mean(precisions)
    mean_recall = np.mean(recalls)

    return mean_precision, mean_recall

def plot_query_token_importance(
    pil_image,
    similarity_maps,
    query_tokens,
    alpha: float = 0.5
) -> None:
    """
    Plot a separate heatmap for each query token in the similarity_maps.

    Args:
        pil_image (PIL.Image.Image): The original image (e.g., loaded via Image.open(...)).
        similarity_maps (torch.Tensor):
            Shape = (num_query_tokens, n_patches_x, n_patches_y).
        query_tokens (List[str]): A list of strings for each token in the query.
        alpha (float): Transparency for the heatmap overlays (0=transparent, 1=opaque).
    """
    # Convert PIL to numpy
    image_np = np.array(pil_image)
    H, W = image_np.shape[:2]

    num_tokens = similarity_maps.size(0)
    assert num_tokens == len(query_tokens), (
        f"The number of query tokens in similarity_maps ({num_tokens}) "
        f"doesn't match the length of query_tokens list ({len(query_tokens)})."
    )

    fig, axs = plt.subplots(1, num_tokens, figsize=(5 * num_tokens, 5))
    if num_tokens == 1:
        # If there's only one token, axs won't be an iterable
        axs = [axs]

    for idx in range(num_tokens):
        # Each similarity_map for a single query token: shape = (n_patches_x, n_patches_y)
        single_map = similarity_maps[idx]  # (n_patches_x, n_patches_y)

        # Upsample to full image size
        single_map_4d = single_map.unsqueeze(0).unsqueeze(0)  # (1,1,n_patches_x, n_patches_y)
        upsampled = F.interpolate(
            single_map_4d,
            size=(H, W),
            mode='bilinear',
            align_corners=False
        )

        # .to(torch.float32) fix if your map is bfloat16
        heatmap = upsampled.squeeze().to(torch.float32).cpu().numpy()  # (H, W)

        # Optionally normalize heatmap (uncomment if desired)
        # heatmap = (heatmap - heatmap.min()) / (heatmap.max() - heatmap.min() + 1e-8)

        # Plot
        axs[idx].imshow(image_np, cmap=None if image_np.ndim == 3 else 'gray')
        axs[idx].imshow(heatmap, cmap='jet', alpha=alpha)
        axs[idx].set_title(f"Query: {query_tokens[idx]}")
        axs[idx].axis('off')

    plt.tight_layout()
    plt.show()

def get_maps_and_embeds(batch_images, batch_queries, model, processor, image, use_qwen=False):
    """
    Computes similarity maps and embeddings from a batch of images and queries using the specified model and processor.

    Args:
        batch_images (dict): A dictionary of batched image inputs processed by the processor.
        batch_queries (dict): A dictionary of batched query inputs processed by the processor.
        model (nn.Module): The model used for computing embeddings.
        processor (Processor): The processor responsible for image and text preprocessing.

    Returns:
        tuple: A tuple containing:
            - original_maps (torch.Tensor): Similarity maps between images and queries
                with shape (num_queries, n_patches_x, n_patches_y).
            - original_image_embeddings (torch.Tensor): Embeddings of the input images.
            - original_query_embeddings (torch.Tensor): Embeddings of the input queries.
    """
    with torch.no_grad():
        original_image_embeddings = model.forward(**batch_images)
        original_query_embeddings = model.forward(**batch_queries)
    if use_qwen:
        n_patches = processor.get_n_patches(image_size=image.size, patch_size=model.patch_size, spatial_merge_size=model.spatial_merge_size)
    else:
        n_patches = processor.get_n_patches(image_size=image.size, patch_size=model.patch_size)
    image_mask = processor.get_image_mask(batch_images)

    # Compute original similarity maps
    original_batched_maps = get_similarity_maps_from_embeddings(
        image_embeddings=original_image_embeddings,
        query_embeddings=original_query_embeddings,
        n_patches=n_patches,
        image_mask=image_mask,
    )
    original_maps = original_batched_maps[0]  # (query_length, n_patches_x, n_patches_y)
    return original_maps, original_image_embeddings, original_query_embeddings

def visualize_token_map(image, original_maps, token_list, token_index=2, cmap="Greens"):
    """
    Visualize a token's attention map in three ways: the original image, the raw attention map with numerical values,
    and an overlay of the attention map on the original image.
    Args:
        image (PIL.Image): The input image to visualize.
        original_maps (torch.Tensor or np.ndarray): Attention maps with shape (num_tokens, height, width).
        token_list (list[str]): List of token strings corresponding to each attention map.
        token_index (int, optional): Index of the token/map to visualize. Defaults to 2.
        cmap (str, optional): Matplotlib colormap name for visualizing the attention maps. Defaults to "Greens".

    The function creates a figure with three subplots:
    1. The original input image
    2. The raw attention map with numerical values annotated
    3. The attention map overlaid on the original image with a colorbar

    Returns:
        None. Displays the visualization using matplotlib.
    """
    # Convert the image to a NumPy array
    image_np = np.array(image)

    # Select the map corresponding to the token
    visual_map = original_maps[token_index]

    # Convert visual_map to NumPy array if it's a tensor
    if isinstance(visual_map, torch.Tensor):
        visual_map = visual_map.cpu().to(dtype=torch.float32).numpy()
    elif not isinstance(visual_map, np.ndarray):
        visual_map = np.array(visual_map)

    # Convert map to a PIL image
    visual_map_pil = Image.fromarray(visual_map)

    # Resize using NEAREST to keep "big pixels"
    visual_map_pil = visual_map_pil.resize(
        (image_np.shape[1], image_np.shape[0]),  # (width, height)
        resample=Image.NEAREST
    )

    # Convert back to NumPy
    resized_map = np.array(visual_map_pil)

    # Create a figure with subplots
    fig, axes = plt.subplots(1, 3, figsize=(15, 6))

    # Display the raw image
    axes[0].imshow(image_np)
    axes[0].set_title("Raw Image")
    axes[0].axis("off")
    # Display the raw map with annotations
    im = axes[1].imshow(visual_map, cmap=cmap)
    axes[1].set_title("Raw Map")
    axes[1].axis("off")

    # Annotate the heatmap
    for i in range(visual_map.shape[0]):
        for j in range(visual_map.shape[1]):
            text = axes[1].text(j, i, f"{visual_map[i, j]:.2f}",
                           ha="center", va="center", color="w" if visual_map[i, j] > visual_map.max() / 2 else "black")

    # Display the overlay plot
    axes[2].imshow(image_np, alpha=1)
    axes[2].imshow(resized_map, cmap=cmap, alpha=0.6)
    axes[2].set_title("Overlay: Image + Map")
    axes[2].axis("off")
    # Add a colorbar for the overlay with matching values to the raw map
    cbar = fig.colorbar(plt.cm.ScalarMappable(cmap=cmap, norm=plt.Normalize(vmin=visual_map.min(), vmax=visual_map.max())), ax=axes[2], shrink=0.8, orientation="vertical")
    cbar.set_label("Map Intensity")
    # Add a title with the token name
    plt.suptitle(f"Token: {token_list[token_index]}")

    # Adjust layout and show
    plt.tight_layout()
    plt.show()

def create_single_patch_image(
    n_patches_x, n_patches_y, patch_size, main_color, special_color, special_patch, special_patch_width=2,
):
    """
    Creates an image composed of colored patches, with one special patch highlighted.

    The image is divided into a grid of n_patches_x by n_patches_y patches, each of size
    patch_size x patch_size pixels. All patches are filled with the main_color, except
    for the special_patch, which is filled with special_color.  The special patch can
    also have a width of more than one patch.
    Args:
        n_patches_x (int): Number of patches horizontally.
        n_patches_y (int): Number of patches vertically.
        patch_size (int): The size (in pixels) of each square patch.
        main_color (list): The [R, G, B] color for most patches.
        special_color (list): The [R, G, B] color for the special patch.
        special_patch (tuple): The (row, col) position of the top-left corner of the special patch (0-indexed).
        special_patch_width (int, optional): The width of the special patch in number of patches. Defaults to 2.

    Returns:
        PIL Image: The generated image.
    """

    # Create a 3D NumPy array for the image
    img_height = n_patches_y * patch_size
    img_width = n_patches_x * patch_size
    image_data = np.zeros((img_height, img_width, 3), dtype=np.uint8)

    # Fill the entire image with the main color
    image_data[:, :] = main_color

    # Assign the special color to the special patch
    special_row, special_col = special_patch
    image_data[
        special_row * patch_size : (special_row + special_patch_width) * patch_size,
        special_col * patch_size : (special_col + special_patch_width) * patch_size
    ] = special_color

    return Image.fromarray(image_data)

def extract_patch_mask(image, patch_size, special_color=[0, 0, 0]):
    """
    Extract a binary mask indicating the location of the special patch.

    Args:
        image (PIL.Image.Image): The input image.
        patch_size (int): The size of each square patch in pixels.
        special_color (list[int]): The RGB color of the special patch.

    Returns:
        np.ndarray: A binary mask of shape (n_patches_y, n_patches_x) indicating
                    the special patch location (1 for special patch, 0 otherwise).
    """
    # Convert the image to a NumPy array
    image_np = np.array(image)

    # Get image dimensions
    img_height, img_width, _ = image_np.shape

    # Compute the number of patches
    n_patches_y = img_height // patch_size
    n_patches_x = img_width // patch_size

    # Initialize the patch mask
    patch_mask = np.zeros((n_patches_y, n_patches_x), dtype=np.int32)

    # Iterate over all patches to locate the special patch
    for row in range(n_patches_y):
        for col in range(n_patches_x):
            # Extract the patch
            patch = image_np[
                row * patch_size : (row + 1) * patch_size,
                col * patch_size : (col + 1) * patch_size
            ]

            # Check if the patch matches the special color
            if np.allclose(patch.mean(axis=(0, 1)), special_color, atol=1e-6):
                patch_mask[row, col] = 1  # Mark this patch as special

    return patch_mask

def evaluate_map_quality(similarity_map, patch_mask):
    """
    Evaluate the quality of a similarity map with respect to a binary patch mask.

    Args:
        similarity_map (np.ndarray): The similarity map (height, width).
        patch_mask (np.ndarray): The binary mask for the patch (1 for black patch, 0 elsewhere).

    Returns:
        dict: Metrics including correlation, peak accuracy, and overlap score.
    """
    # Flatten the map and mask for easier computation
    sim_map_flat = similarity_map.flatten()
    patch_mask_flat = patch_mask.flatten()

    # (A) Correlation
    correlation = np.corrcoef(sim_map_flat, patch_mask_flat)[0, 1]

    # (B) Peak Signal Location
    max_location = np.unravel_index(np.argmax(similarity_map), similarity_map.shape)
    expected_location = np.unravel_index(np.argmax(patch_mask), patch_mask.shape)
    peak_accuracy = 1 if max_location == expected_location else 0

    # (C) Normalized Map Overlap
    black_patch_score = similarity_map[patch_mask == 1].mean()
    background_score = similarity_map[patch_mask == 0].mean()
    overlap_score = black_patch_score / (background_score + 1e-8)  # Avoid division by zero

    # Return all metrics
    return {
        "correlation": correlation,
        "peak_accuracy": peak_accuracy,
        "overlap_score": overlap_score,
    }

def create_single_patch_image_with_text(
    n_patches_x,
    n_patches_y,
    patch_size,
    main_color,
    special_color,
    special_patch,
    text="Hello",
    text_color=(255, 255, 255),
    special_patch_width=2,
    font_size=16,
):
    """
    Creates an image composed of colored patches, but places a single word (or text)
    inside the "special" patch area.
    """
    # Create a 3D NumPy array for the image
    img_height = n_patches_y * patch_size
    img_width = n_patches_x * patch_size
    image_data = np.zeros((img_height, img_width, 3), dtype=np.uint8)

    # Fill the entire image with the main color
    image_data[:, :] = main_color

    # Assign the special color to the special patch area
    special_row, special_col = special_patch
    image_data[
        special_row * patch_size : (special_row + special_patch_width) * patch_size,
        special_col * patch_size : (special_col + special_patch_width) * patch_size
    ] = special_color

    # Convert to a Pillow Image so we can draw on it
    img = Image.fromarray(image_data)
    draw = ImageDraw.Draw(img)

    # Load font with specified size
    try:
        url = "https://github.com/google/fonts/raw/main/apache/roboto/Roboto-Regular.ttf"
        response = requests.get(url)
        font_path = "Roboto-Regular.ttf"
        with open(font_path, "wb") as font_file:
            font_file.write(response.content)

        font = ImageFont.truetype(font_path, font_size)
    except IOError:
        font = ImageFont.load_default()
    # Calculate the center of the special patch in pixel coordinates
    patch_center_x = (
        special_col * patch_size
        + (special_patch_width * patch_size) // 2
    )
    patch_center_y = (
        special_row * patch_size
        + (special_patch_width * patch_size) // 2
    )

    # Calculate text bounding box to center the text
    text_bbox = draw.textbbox((0, 0), text, font=font)
    text_width = text_bbox[2] - text_bbox[0]
    text_height = text_bbox[3] - text_bbox[1]

    text_x = patch_center_x - text_width // 2
    text_y = patch_center_y - text_height // 2

    # Place text in the center of the special patch
    draw.text((text_x, text_y), text, fill=text_color, font=font)

    return img

def write_on_images():

    # Importing the PIL library
    from PIL import Image
    from PIL import ImageDraw

    # Open an Image
    img = Image.open('kirby.jpeg')

    # Call draw Method to add 2D graphics in an image
    I1 = ImageDraw.Draw(img)

    # Add Text to an image
    I1.text((28, 36), "nice Car", fill=(255, 0, 0))

    # Display edited image
    img.show()

    # Save the edited image
    img.save("car2.png")

Instruction: add font_size param

import inspect
import re
import pytest
import importlib
import os
import sys
import requests
from unittest.mock import MagicMock, patch
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont

def mock_module_dependencies(module):
    """Mock any missing dependencies in the module"""
    # Mock colpali_engine.interpretability imports if they don't exist
    if not hasattr(module, "get_similarity_maps_from_embeddings") and hasattr(
        module, "get_maps_and_embeds"
    ):
        # Create a mock for get_similarity_maps_from_embeddings
        mock_get_maps = Mock()
        mock_get_maps.return_value = [torch.rand(1, 5, 5)]  # Return random tensor
        module.get_similarity_maps_from_embeddings = mock_get_maps
        print("Mocked get_similarity_maps_from_embeddings function")

def test_function_exists(implementation):
    """Test that there is a function for writing text on images in the implementation."""
    impl_name, module = implementation

    # Check for various possible function names that would add text to images
    write_func = locate_text_writing_function(module)

    assert (
        write_func is not None
    ), f"Implementation {impl_name} does not have a function for writing text on images"

def locate_text_writing_function(module):
    print(module)
    return module.write_on_images

def get_font_size_param_name(func):
    """Determine which parameter name is used for font size."""
    signature = inspect.signature(func)

    # Common parameter names for font size
    font_param_names = ["font_size", "fontsize", "size", "font_sz", "text_size"]

    # Check for any of these parameter names
    for param in font_param_names:
        if param in signature.parameters:
            return param

    return None

def test_text_function_has_font_size_parameter(implementation):
    """Test that the text writing function has a font_size parameter."""
    impl_name, module = implementation

    write_func = locate_text_writing_function(module)
    if not write_func:
        pytest.fail(
            f"Implementation {impl_name} doesn't have a function for writing text on images"
        )

    # Get the signature of the function
    signature = inspect.signature(write_func)
    parameters = signature.parameters

    # Check for font size parameter using any common name
    param_name = get_font_size_param_name(write_func)

    assert (
        param_name is not None
    ), f"Implementation {impl_name} does not have a font size parameter in {write_func.__name__}"

pillow
numpy
matplotlib
pytest
pytest-mock
scikit-learn
torch
seaborn
requests
colpali-engine
einops

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            # r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output


from telegram import Update, BotCommand
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes
import asyncio

async def hello(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
    keyboard = [
        [
            InlineKeyboardButton("💬 ChatGPT", url="https://chat.openai.com"),
            InlineKeyboardButton("🧠 Perplexity", url="https://www.perplexity.ai")
        ],
        [
            InlineKeyboardButton("🤖 Claude (Anthropic)", url="https://claude.ai"),
            InlineKeyboardButton("🌈 Google Bard", url="https://bard.google.com")
        ],
        [
            InlineKeyboardButton("🚀 HuggingChat", url="https://huggingface.co/chat")
        ]
    ]
    reply_markup = InlineKeyboardMarkup(keyboard)
    user_name = update.effective_user.first_name
    await update.message.reply_text(f"Hello, **{user_name}**! Choose your favorite *AI search engine* 🌐", reply_markup=reply_markup, parse_mode="Markdown")
async def set_bot_commands(bot):
    commands = [
        BotCommand("hello", "Greetings from the bot!"),
        BotCommand("help", "List of all commands"),
    ]
    await bot.set_my_commands(commands)

async def set_bot_profile(bot):
    profile_name = "Casa🔴Latina bot"
    await bot.set_my_name(profile_name)

async def main():
    app = ApplicationBuilder().token("7614506611:AAEIsUUvhNO7_BOk-R3SIidC85lmjD3tXuE").build()

    # Set bot commands
    await set_bot_commands(app.bot)

    # Set bot profile
    await set_bot_profile(app.bot)

    app.add_handler(CommandHandler("start", hello))
    await app.run_polling()

if __name__ == "__main__":
    asyncio.run(main())

Instruction: RuntimeError: Cannot close a running event loop sys:1: RuntimeWarning: coroutine 'Application.shutdown' was never awaited sys:1: RuntimeWarning: coroutine 'Application.initialize' was never awaited

import asyncio
import inspect
import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from telegram import Update, BotCommand
from telegram.ext import ApplicationBuilder, Application, CommandHandler, ContextTypes

@pytest.fixture
def mock_telegram_update():
    """Create a mock Telegram update object"""
    update = MagicMock(spec=Update)
    update.effective_user = MagicMock()
    update.effective_user.first_name = "TestUser"
    update.message = MagicMock()
    update.message.reply_text = AsyncMock()
    return update

@pytest.fixture
def mock_bot():
    """Create a mock bot with AsyncMock methods for telegram API calls"""
    bot = MagicMock()
    bot.set_my_commands = AsyncMock()
    bot.set_my_name = AsyncMock()
    return bot

def test_required_imports(implementation):
    """Test that necessary imports are present in the implementation"""
    impl_name, module = implementation

    # Check import patterns in source code instead of direct module attributes
    source_code = inspect.getsource(module)
    imports_pattern = (
        "from telegram import" in source_code and
        "InlineKeyboardButton" in source_code and
        "InlineKeyboardMarkup" in source_code
    )

    assert imports_pattern, f"{impl_name}: Missing imports for InlineKeyboardButton/InlineKeyboardMarkup"

def test_asyncio_usage(implementation):
    """Test that the implementation is using asyncio correctly"""
    impl_name, module = implementation

    # Check that the main function is defined as async
    assert asyncio.iscoroutinefunction(module.main), f"{impl_name}: main function should be async"

    # Check if the implementation uses asyncio.run(main()) in the entry point
    main_source = inspect.getsource(module)
    assert "if __name__ == \"__main__\":" in main_source, f"{impl_name}: Missing proper entry point"
    assert "asyncio.run(main())" in main_source, f"{impl_name}: Not using asyncio.run for the entry point"

@patch('telegram.ext.ApplicationBuilder.build')
def test_hello_function(mock_builder, implementation, mock_telegram_update, mock_bot):
    """Test that the hello function works as expected"""
    impl_name, module = implementation

    # Verify that the hello function is a coroutine
    assert asyncio.iscoroutinefunction(module.hello), f"{impl_name}: hello function should be async"

    # Mock the app and bot for any operations
    mock_app = MagicMock()
    mock_app.bot = mock_bot
    mock_builder.return_value = mock_app

    # Test the function with a mock context
    mock_context = MagicMock(spec=ContextTypes.DEFAULT_TYPE)

    # Create a new event loop for the test
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    try:
        # Create mock classes at the module level if they don't exist
        # Fix: Instead of trying to patch existing attributes, add them temporarily
        telegram_module = MagicMock()

        # Set up mocks for telegram objects with proper patching approach
        with patch('telegram.InlineKeyboardButton', MagicMock()), \
             patch('telegram.InlineKeyboardMarkup', MagicMock()):

            # Run the hello function
            loop.run_until_complete(module.hello(mock_telegram_update, mock_context))

            # Verify the function called reply_text
            mock_telegram_update.message.reply_text.assert_called_once()

            # Check if the user's name is in the message
            args, kwargs = mock_telegram_update.message.reply_text.call_args
            assert "TestUser" in args[0], f"{impl_name}: hello function should include user's name"

            # Check if reply_markup is provided
            assert "reply_markup" in kwargs, f"{impl_name}: hello function should include reply_markup"
            assert kwargs.get("parse_mode") == "Markdown", f"{impl_name}: Markdown should be used as parse_mode"

    finally:
        loop.close()

def test_application_lifecycle_components(implementation):
    """Test that the application lifecycle elements are present"""
    impl_name, module = implementation

    # Analyze the main function
    main_source = inspect.getsource(module.main)

    # Check for application initialization
    has_initialize = "await app.initialize()" in main_source

    # Check for application start
    has_start = "await app.start()" in main_source

    # Check for polling
    has_polling = (
        "await app.updater.start_polling()" in main_source or
        "await app.run_polling()" in main_source  # Accept this despite it being problematic
    )

    # Assert each component separately for clearer error messages
    assert has_initialize or has_polling, f"{impl_name}: Missing application initialization"
    assert has_start or has_polling, f"{impl_name}: Missing application start"
    assert has_polling, f"{impl_name}: Missing polling mechanism"

@patch('telegram.ext.ApplicationBuilder.build')
def test_command_handler_registration(mock_builder, implementation, mock_bot):
    """Test that command handlers are properly registered"""
    impl_name, module = implementation

    # Mock the application builder and app instance
    mock_app = MagicMock(spec=Application)
    mock_app.add_handler = MagicMock()
    mock_app.bot = mock_bot
    mock_builder.return_value = mock_app

    # Create an async mock for the main function that will immediately return
    async def mock_main_impl():
        # Setting up a simplified test version of main that registers handlers
        app = mock_builder()

        # Directly call the functions that would be in main()
        with patch('telegram.BotCommand', MagicMock()):
            await module.set_bot_commands(app.bot)
            await module.set_bot_profile(app.bot)

        # Register at least one handler (simulate what the original main would do)
        app.add_handler(CommandHandler("start", module.hello))

        return app  # Return the app for inspection

    # Create a new event loop for the test
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)

    try:
        # Patch module.main to use our mock_main_impl
        with patch.object(module, 'main', mock_main_impl):
            # Run just the mocked version of main
            app = loop.run_until_complete(module.main())

        # Verify that add_handler was called
        assert mock_app.add_handler.called, f"{impl_name}: Command handler not registered"

        # Verify the bot methods were called
        mock_bot.set_my_commands.assert_called_once()
        mock_bot.set_my_name.assert_called_once()

    finally:
        loop.close()

def test_graceful_shutdown(implementation):
    """Test that the implementation handles graceful shutdown"""
    impl_name, module = implementation

    main_source = inspect.getsource(module.main)

    # Check for try/finally pattern for cleanup
    has_try_finally = "try:" in main_source and "finally:" in main_source

    # Check for explicit shutdown calls
    has_explicit_shutdown = (
        "await app.stop()" in main_source or
        "await app.shutdown()" in main_source
    )

    # Check for implicit shutdown via idle
    has_idle_shutdown = "await app.updater.idle()" in main_source

    assert has_try_finally or has_explicit_shutdown or has_idle_shutdown, (
        f"{impl_name}: Missing proper application shutdown handling"
    )

def test_set_bot_commands_and_profile(implementation):
    """Test that bot commands and profile are set correctly"""
    impl_name, module = implementation

    # Verify that the functions are coroutines
    assert asyncio.iscoroutinefunction(module.set_bot_commands), f"{impl_name}: set_bot_commands should be async"
    assert asyncio.iscoroutinefunction(module.set_bot_profile), f"{impl_name}: set_bot_profile should be async"

    # Check that these functions are called in main
    main_source = inspect.getsource(module.main)
    assert "await set_bot_commands" in main_source, f"{impl_name}: set_bot_commands not called in main"
    assert "await set_bot_profile" in main_source, f"{impl_name}: set_bot_profile not called in main"

def test_proper_polling_approach(implementation):
    """Test that the implementation uses a pattern that avoids the coroutine never awaited error"""
    impl_name, module = implementation

    main_source = inspect.getsource(module.main)

    # Option 1: Using the proper application lifecycle with separate method calls
    correct_approach_1 = (
        "await app.initialize()" in main_source and
        "await app.start()" in main_source and
        "await app.updater.start_polling()" in main_source
    )

    # Option 2: Using updater.idle() which also handles signals
    correct_approach_2 = "await app.updater.idle()" in main_source

    # Option 3: Using run_polling with proper shutdown handling
    correct_approach_3 = (
        "await app.run_polling()" in main_source and
        ("try:" in main_source and "finally:" in main_source)
    )

    # At least one approach should be used
    assert correct_approach_1 or correct_approach_2 or correct_approach_3, (
        f"{impl_name}: Not using a proper approach to avoid 'coroutine never awaited' RuntimeError"
    )

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            # r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output


from ast import Add
from asyncio import wait
from curses import COLOR_BLUE, COLOR_RED
from re import A
from shutil import move
from glm import degrees
from manim import *
from numpy import size, square

class Project(Scene):
    def construct(self):
        text = Tex("Double Angle")
        self.play( Write(text))

        self.wait(5)

        transform_text = Tex("What is Double Angle?")
        transform_text.to_corner(UP)
        box = SurroundingRectangle(transform_text)
        box.set_color(WHITE)
        box.set_stroke(width=1.5)
        self.play(
            Transform(text, transform_text)
        )
        self.wait(0.5)
        self.play(Create(box))

        explanation = Paragraph("A double angle is an angle measurement", "that has been multiplied by 2 or added to itself.", line_spacing=0.5, font_size=32)
        explanation.move_to(ORIGIN)

        self.play(
            Write(explanation)
        )

        self.wait(3)

        self.play(
            Transform(explanation, explanation.copy().shift(UP))
        )

        trig_cos2 = MathTex(
            r"\cos2x = \cos^2x - \sin^2x",

            substrings_to_isolate=["cos2x"]
            )
        trig_cos2.set_color_by_tex("cos2x", BLUE)
        trig_cos2.move_to(DOWN)
        transform_formula = Tex("Double Angle Formula")
        transform_formula.to_corner(UP)

        self.wait(1)

        self.play(
        Write(trig_cos2)
        )

        self.wait(2)

        self.play(
            FadeOut(trig_cos2, explanation)
        )

        self.wait(1)

        axes = Axes(
            x_range=[-2, 2, 2],
            y_range=[-2, 2, 2],
            x_length=4,
            y_length=4,
        )
        self.add(axes)

        # 単位円の作成
        circle = Circle(radius=2, color=BLUE)
        self.add(circle)

        # 原点 (Origin)
        dot = Dot(ORIGIN, color=RED)
        self.add(dot)

        # 角度を表す線分 (Line representing the angle)
        line = Line(ORIGIN, RIGHT * 2)
        self.add(line)

        # 角度のラベル (Angle label)
        # Create an Arc for the angle
        angle = Arc(
            radius=2,
            start_angle=0,  # Start at the positive x-axis
            angle=line.get_angle(),  # Use line's angle
            arc_center=ORIGIN,
            color=GREEN
        )
        angle_label = MathTex(r"\theta = 0^{\circ}").next_to(angle, RIGHT) # Changed Tex to MathTex and added \\
        self.add(angle, angle_label)

        intersection_dot = Dot(color=YELLOW)

        angle_tracker = ValueTracker(0)

        def update_line(mobject):
            mobject.become(Line(ORIGIN, RIGHT * 2).rotate(angle_tracker.get_value(), about_point=ORIGIN))

        def update_angle(mobject):
            mobject.become(Arc(
                radius=2,
                start_angle=0,
                angle=angle_tracker.get_value(),
                arc_center=ORIGIN,
                color=GREEN
            ))

        line.add_updater(update_line)
        angle.add_updater(update_angle)

        # Update the angle label
        def update_label(mobject):
            angle_in_degrees = np.degrees(angle_tracker.get_value())
            mobject.become(MathTex(rf"\\theta = {angle_in_degrees:.0f}^{{\circ}}")) # Added double brackets
            mobject.next_to(angle, RIGHT)

        angle_label.add_updater(update_label)

        def update_intersection_dot(mobject):
            angle = angle_tracker.get_value()
            x = 2 * np.cos(angle)  # x-coordinate on the circle
            y = 2 * np.sin(angle)  # y-coordinate on the circle
            mobject.move_to([x, y, 0])

        intersection_dot.add_updater(update_intersection_dot)

        self.add(intersection_dot)
        # Animate the angle
        self.play(
            angle_tracker.animate.set_value(PI / 6),
            run_time=2
        )
        self.wait(3)

        line.clear_updaters()
        intersection_dot.clear_updaters()
        angle.clear_updaters()
        angle_label.clear_updaters()

        # Change their color to indicate they are fixed
        fixed_line = line.copy().set_color(ORANGE)
        fixed_dot = intersection_dot.copy().set_color(ORANGE)
        fixed_angle = angle.copy().set_color(ORANGE)
        self.add(fixed_line, fixed_dot, fixed_angle)

        # Prepare a new line for the next animation
        new_line = Line(ORIGIN, RIGHT * 2, color=GREEN)
        new_intersection_dot = Dot(color=YELLOW)
        new_angle = Arc(
            radius=0.5,
            start_angle=PI / 6,  # Start from 30 degrees
            angle=0,
            arc_center=ORIGIN,
            color=GREEN
        )
        new_label = MathTex(rf"\theta = 30^\circ").next_to(new_angle, RIGHT).set_color(ORANGE)

        # Updaters for the new objects
        new_line.add_updater(lambda m: m.become(
            Line(ORIGIN, RIGHT * 2).rotate(angle_tracker.get_value(), about_point=ORIGIN)
        ))

        new_intersection_dot.add_updater(lambda m: m.move_to([
            2 * np.cos(angle_tracker.get_value()),
            2 * np.sin(angle_tracker.get_value()),
            0
        ]))

        new_angle.add_updater(lambda m: m.become(
            Arc(
                radius=0.5,
                start_angle=0,
                angle=angle_tracker.get_value(),
                arc_center=ORIGIN,
                color=GREEN
            )
        ))

        new_label.add_updater(lambda m: m.become(
            MathTex(rf"\theta = {np.degrees(angle_tracker.get_value()):.0f}^\circ").next_to(new_angle, LEFT)
        ))

        # Add the new objects
        self.add(new_line, new_intersection_dot, new_angle, new_label)

        # Animate from 30 degrees to 60 degrees
        self.play(
            angle_tracker.animate.set_value(PI / 3),  # 60 degrees
            run_time=2
        )
        self.wait(1)

        self.wait(10)

        self.play(
            FadeOut(circle, dot, line, angle, angle_label, axes, line, angle, intersection_dot, angle_label, new_line, new_angle, new_label, new_intersection_dot, fixed_line, fixed_angle, fixed_dot, angle_tracker)
        )

        self.play(
            FadeOut(transform_text, explanation),
            Transform(trig_cos2 , trig_cos2.copy().shift(UP + UP + UP)),
            Transform(text, transform_formula),
        )
        self.wait(2)

        cos_xx = MathTex(
            r"\cos2x = \cos(A+B)"
        )
        cos_xx.move_to(ORIGIN + UP)

        cos_ab = MathTex (
            r"\cos(A+B) =(\cos A \cdot \cos B) - (\sin A \cdot \sin B)"
        )
        cos_ab.move_to(ORIGIN)

        let_AB = Tex("Let A = B")
        let_AB.move_to(ORIGIN + DOWN)

        ab_simple = MathTex(
            r"\cos(A+A) = \cos^2A - \sin^2A"
        )
        ab_simple.move_to(ORIGIN + DOWN + DOWN)

        ab_finalize = MathTex(
            r"= 1-2\sin^2x"
        )
        ab_finalize.move_to(ORIGIN + DOWN + DOWN + DOWN + RIGHT)

        self.play(
            Write(cos_xx)
        )
        self.wait(0.5)
        self.play(
            Write(cos_ab),
        )
        self.wait(0.5)
        self.play(
            Write(let_AB)
        )
        self.wait(0.5)
        self.play(
            Write(ab_simple)
        )
        self.wait(0.5)
        self.play(
            Write(ab_finalize)
        )

        arrow = Arrow(2*UP, 2*DOWN)
        VGroup(arrow).set_x(0).arrange(buff=2)
        arrow.move_to(ORIGIN + RIGHT + RIGHT + RIGHT + RIGHT + RIGHT + RIGHT)
        self.play(Write(arrow))

        self.wait(15)

        self.play(
            FadeOut(text, transform_text, trig_cos2, cos_xx, cos_ab, let_AB, ab_simple, ab_finalize, arrow, box, transform_formula)
        )

        self.wait(1)
        #moving to the explanation of example

        #What is proof in Math?
        proof = Tex("What is proof?", font_size = 48)
        self.play(Write(proof))
        self.wait(3)

        self.play(
            Transform(proof, proof.copy().shift(UP).shift(UP))
        )

        proof_exp = Paragraph("In trigonometry, a proof is a way to show that ", "two trigonometric expressions are equivalent, regardless of the angle. ","This process is called validating or proving trigonometric identities.", font_size=28)
        self.play(Write(proof_exp))

        self.wait(8)
        self.play(
            FadeOut(proof, proof_exp)
        )

        #starting with Sin and Cos graph identity

        ax = Axes()
        sine = ax.plot(np.sin, color = RED)
        cosine = ax.plot(np.cos, color = BLUE)
        self.play(
            FadeIn(ax, sine, cosine)
            )

        red_square = Square(fill_opacity = 1, side_length=0.5, fill_color = RED_C).to_corner(UL)
        blue_square = Square(fill_opacity=1, side_length=0.5, fill_color=BLUE_C).to_corner(UL - DOWN)

        self.play(DrawBorderThenFill(red_square))
        self.play(DrawBorderThenFill(blue_square))
        text_sin = MathTex(r"\sin(x)")
        text_cos = MathTex(r"\cos(x)")
        text_sin.next_to(Square(fill_opacity=1, side_length=0.5, fill_color=RED_C).to_corner(UL))
        text_cos.next_to(Square(fill_opacity=1, side_length=0.5, fill_color=BLUE_C).to_corner(UL - DOWN))
        # Correct usage of next_to: Multiply RIGHT by a scala

        self.play(Write(text_sin))
        self.wait(0.5)

        self.play(Write(text_cos))
        self.wait(0.5)

        self.wait(8)
        self.play(FadeOut(sine, cosine, text_sin, text_cos, ax, red_square, blue_square))
        self.wait(2)

        prob_cos = Tex(r"Prove that $\cos\left(x - \frac{\pi}{2}\right)$ is the same as $\sin x$")
        self.play(Write(prob_cos))
        self.wait(2)

        self.play(
            Transform(prob_cos, prob_cos.copy().to_corner(UP))
        )
        self.wait(10)

        step1 = Tex(r"1. Make balance equation $\cos\left(x - \frac{\pi}{2}\right) = \sin x$")
        step2 = Tex("2. Identify which side is easier to change form, or simplify.")
        step3 = Tex("3. Formulate and make it equal to the other side.")

        steps = VGroup(step1, step2, step3).arrange(DOWN, aligned_edge=LEFT)
        steps.move_to(ORIGIN)
        steps.next_to(prob_cos, DOWN, buff=0.5)

        self.play(
            Write(steps)
        )

        self.wait(3)

        self.play(Circumscribe(step1, Rectangle, time_width=4))

        self.play(
            FadeOut(step2, step3)
        )

        step1_exp = MathTex(r"\cos\left(x-\frac{\pi}{2}\right) = \sin x")
        step1_exp.move_to(ORIGIN)

        self.play(
            Write(step1_exp)
        )

        self.wait(6)

        self.play(
            FadeOut(step1, step1_exp),
        )

        self.wait(1)

        self.play(
            FadeIn(steps),
        )

        self.wait(3)

        self.play(
            Circumscribe(step2, Rectangle, time_width=4)
        )

        self.play(
            FadeOut(step1, step3),
            Transform(step2, step2.copy().shift(UP))
        )

        self.wait(3)

        step2_exp = MathTex(r"\cos\left(x-\frac{\pi}{2}\right)", color=BLUE)
        step2_exp.move_to(ORIGIN)
        self.wait(2)

        self.play(Write(step2_exp))

        self.wait(4)

        self.play(
            Transform(step2, step2.copy().shift(DOWN)),
            FadeOut(step2_exp)
        )

        self.play(FadeIn(step1, step3))

        self.wait(1)

        self.wait(2)

        self.play(
            Circumscribe(step3, Rectangle, time_width=4)
                )
        self.play(
            FadeOut(step1, step2),
            Transform(step3, step3.copy().shift(UP + UP))
                )
        self.wait(3)
        step3_exp = MathTex(r"\cos\left(x-\frac{\pi}{2}\right) = \cos(x) \cos\left(\frac{\pi}{2}\right) + \sin(x) \sin\left(\frac{\pi}{2}\right)")
        step3_exp.move_to(ORIGIN)

        # Animate each part of the equation separately
parts = step3_exp.get_parts_by_tex(["=", r"\cos", r"\sin", "+"])
        self.play(AnimationGroup(
            *[FadeIn(part, shift=UP*0.5) for part in parts],
            lag_ratio=0.2
        ))
        self.wait(2)

        step3_exp2 = MathTex(r"= \cos(x) \cdot 0 + \sin(x) \cdot 1")
        step3_exp2.next_to(step3_exp, DOWN)
        self.play(
            TransformFromCopy(step3_exp, step3_exp2),
            run_time=1.5
        )
        self.wait(2)

        step3_exp3 = MathTex(r"= 0 + \sin(x)")
        step3_exp3.next_to(step3_exp2, DOWN)
        self.play(
            ReplacementTransform(step3_exp2.copy(), step3_exp3),
            run_time=1.5
        )
        self.wait(2)

        step3_exp4 = MathTex(r"= \sin(x)")
        step3_exp4.next_to(step3_exp3, DOWN)
        self.play(
            TransformMatchingShapes(step3_exp3.copy(), step3_exp4),
            run_time=1.5
        )
        self.wait(2)

        # Create highlighting effect with pulsing animation
        self.play(
            *[ApplyMethod(exp.scale, 1.2, rate_func=there_and_back) for exp in [step3_exp, step3_exp2, step3_exp3, step3_exp4]],
            *[exp.animate.set_color(YELLOW) for exp in [step3_exp, step3_exp2, step3_exp3, step3_exp4]],
            run_time=2
        )
        self.wait(1)

        # Smooth transition with spiral effect
        self.play(
            *[FadeOut(exp, shift=LEFT) for exp in [step3_exp, step3_exp2, step3_exp3]],
            step3_exp4.animate.move_to(ORIGIN).scale(1.2),
            run_time=1.5
        )
        self.wait(2)

        final_proof = Tex(r"Therefore, $\cos\left(x - \frac{\pi}{2}\right) = \sin x$ is proven.")
        final_proof.next_to(step3_exp4, DOWN)

        # Create dramatic reveal for final proof
        self.play(
            Write(final_proof, run_time=2),
            Flash(final_proof, color=BLUE, flash_radius=0.5),
            step3_exp4.animate.set_color(GREEN)
        )
        self.wait(5)

        # Final emphasis animation
        self.play(
            Indicate(final_proof, color=YELLOW, scale_factor=1.2),
            run_time=2
        )
        self.wait(2)

        self.play(
            FadeOut(final_proof, step3, step3_exp4, prob_cos)
        )

# Create axes and graphs
        ax = Axes(
            x_range=[-2*PI, 2*PI, PI/2],
            y_range=[-2, 2, 1],
            x_length=10,
            y_length=6,
        )

        # Plot sin and cos
        sine = ax.plot(lambda x: np.sin(x), color=RED)
        cosine = ax.plot(lambda x: np.cos(x), color=BLUE)

        # Labels
        sin_label = MathTex(r"\sin(x)", color=RED).next_to(ax, UP)
        cos_label = MathTex(r"\cos(x)", color=BLUE).next_to(sin_label, RIGHT)

        # Add everything to scene
        self.play(Create(ax))
        self.play(
            Create(sine),
            Create(cosine),
            Write(sin_label),
            Write(cos_label)
        )
        self.wait(2)

        # Show translation
        shift_text = Tex(r"Shifting $\cos(x)$ left by $\frac{\pi}{2}$ gives us $\sin(x)$").to_edge(UP)
        self.play(
            Write(shift_text),
            FadeOut(sin_label, cos_label)
        )

        # Create shifted cosine
        shifted_cosine = ax.plot(
            lambda x: np.cos(x - PI/2),
            color=GREEN
        )

        shifted_label = MathTex(r"\cos(x-\frac{\pi}{2})", color=GREEN).next_to(ax, DOWN)

        translated_cosine = VGroup(shifted_cosine, shifted_label)

        # Animate the shift
        self.play(
            Transform(
                cosine,
                shifted_cosine
            ),
            Write(shifted_label)
        )

# Fade out the original cosine graph
        self.play(FadeOut(cosine))

        self.wait(0.5)

                # Cleanup
        self.play(
            FadeOut(ax, sine, shift_text, translated_cosine)
                )

        self.wait(3)

Instruction: fix the errors and i am using Manim

import pytest
import inspect
import re
import numpy as np
import types
from unittest.mock import patch, MagicMock

def skip_if_no_project_class(func):
    """Decorator to skip test if Project class doesn't exist."""
    def wrapper(implementation):
        impl_name, module = implementation
        if not hasattr(module, 'Project'):
            pytest.skip(f"{impl_name} doesn't have a Project class")
        return func(implementation)
    return wrapper

def test_project_class_exists(implementation):
    """Test that the Project class exists in the implementation."""
    impl_name, module = implementation

    # Check if any class inherits from Scene (it might not be named Project)
    for name, obj in inspect.getmembers(module):
        if inspect.isclass(obj):
            if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                # Found a class that inherits from Scene
                return

    assert hasattr(module, 'Project'), f"{impl_name} should have a Project class or a class that inherits from Scene"

@skip_if_no_project_class
def test_project_inherits_from_scene(implementation):
    """Test that Project class inherits from Scene."""
    impl_name, module = implementation

    # If module doesn't have Project, look for any class that inherits from Scene
    if not hasattr(module, 'Project'):
        for name, obj in inspect.getmembers(module):
            if inspect.isclass(obj):
                if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                    # Found a class that inherits from Scene
                    return
        pytest.skip(f"{impl_name} doesn't have any class that inherits from Scene")

    # Need to handle case where manim can't be imported
    try:
        from manim import Scene
        assert issubclass(module.Project, Scene), f"{impl_name}'s Project class should inherit from Scene"
    except ImportError:
        # If manim isn't available, check the bases of Project
        assert len(module.Project.__bases__) > 0, f"{impl_name}'s Project class should inherit from Scene"
        assert module.Project.__bases__[0].__name__ == "Scene", f"{impl_name}'s Project should inherit from Scene"

@skip_if_no_project_class
def test_construct_method_exists(implementation):
    """Test that the construct method exists in the Project class."""
    impl_name, module = implementation

    # Find the Scene subclass (might not be named Project)
    scene_class = None
    if hasattr(module, 'Project'):
        scene_class = module.Project
    else:
        for name, obj in inspect.getmembers(module):
            if inspect.isclass(obj):
                if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                    scene_class = obj
                    break

    assert scene_class is not None, f"{impl_name} should have a Scene subclass"
    assert hasattr(scene_class, 'construct'), f"{impl_name}'s Scene subclass should have a construct method"

    # Check if construct is a method in a more reliable way
    # This handles both instance methods and class methods
    construct_attr = getattr(scene_class, 'construct')
    is_method = (inspect.isfunction(construct_attr) or
                inspect.ismethod(construct_attr) or
                isinstance(construct_attr, types.MethodType))

    assert is_method, f"{impl_name}'s construct should be a method"

@skip_if_no_project_class
def test_step3_exp_animation_fixed(implementation):
    """Test that the errors in the animation of step3_exp have been fixed."""
    impl_name, module = implementation
    try:
        # Find the Scene subclass
        scene_class = None
        if hasattr(module, 'Project'):
            scene_class = module.Project
        else:
            for name, obj in inspect.getmembers(module):
                if inspect.isclass(obj):
                    if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                        scene_class = obj
                        break

        assert scene_class is not None, f"{impl_name} should have a Scene subclass"
        source_code = inspect.getsource(scene_class.construct)

        # Check if the problematic line with get_parts_by_tex has been modified properly
        animation_fixed = False

        # Approach 1: Check for the use of Write instead of the problematic approach
        pattern1 = re.compile(r'self\.play\(\s*Write\(step3_exp\)|Write\(step3_exp\)')

        # Approach 2: Check for corrected get_parts_by_tex usage
        pattern2 = re.compile(r'parts\s*=\s*step3_exp')

        # Approach 3: Check for using standard Animation approach
        pattern3 = re.compile(r'self\.play\(\s*[^)]*step3_exp')

        # Approach 4: Check for any animation involving step3_exp
        pattern4 = re.compile(r'step3_exp.*\)')

        # Approach 5: TransformMatchingTex approach
        pattern5 = re.compile(r'TransformMatchingTex\([^,]+,\s*step3_exp')

        if (pattern1.search(source_code) or pattern2.search(source_code) or
            pattern3.search(source_code) or pattern4.search(source_code) or
            pattern5.search(source_code)):
            animation_fixed = True

        assert animation_fixed, f"{impl_name} should fix the animation issue with step3_exp"
    except (AttributeError, TypeError):
        pytest.skip(f"{impl_name} can't access construct method source")

@skip_if_no_project_class
def test_transform_matching_tex_usage(implementation):
    """Test for proper TransformMatchingTex usage or alternative."""
    impl_name, module = implementation
    try:
        # Find the Scene subclass
        scene_class = None
        if hasattr(module, 'Project'):
            scene_class = module.Project
        else:
            for name, obj in inspect.getmembers(module):
                if inspect.isclass(obj):
                    if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                        scene_class = obj
                        break

        assert scene_class is not None, f"{impl_name} should have a Scene subclass"
        source_code = inspect.getsource(scene_class.construct)

        # Check for either TransformMatchingTex or alternatives like Write, ReplacementTransform, etc.
        transform_fixed = False

        patterns = [
            r'TransformMatchingTex\(',
            r'TransformMatchingShapes\(',
            r'ReplacementTransform\(',
            r'Transform\(',
            r'TransformFromCopy\(',
            r'Write\('  # Simple alternative
        ]

        for pattern in patterns:
            if re.search(pattern, source_code):
                transform_fixed = True
                break

        assert transform_fixed, f"{impl_name} should use proper transformation animations"
    except (AttributeError, TypeError):
        pytest.skip(f"{impl_name} can't access construct method source")

def test_no_syntax_errors(implementation):
    """Test that there are no syntax errors in the implementation."""
    impl_name, module = implementation
    # If the module was successfully imported, it has no syntax errors
    assert module is not None, f"{impl_name} should not have syntax errors"

@skip_if_no_project_class
def test_animation_transformations(implementation):
    """Test that proper animation transformations are used."""
    impl_name, module = implementation
    try:
        # Find the Scene subclass
        scene_class = None
        if hasattr(module, 'Project'):
            scene_class = module.Project
        else:
            for name, obj in inspect.getmembers(module):
                if inspect.isclass(obj):
                    if hasattr(obj, '__bases__') and any(base.__name__ == 'Scene' for base in obj.__bases__):
                        scene_class = obj
                        break

        assert scene_class is not None, f"{impl_name} should have a Scene subclass"
        source_code = inspect.getsource(scene_class.construct)

        # Check for Transform, Write, FadeIn, FadeOut
        animation_count = 0
        animations = [r'Transform\(', r'Write\(', r'FadeIn\(', r'FadeOut\(']

        for animation in animations:
            if re.search(animation, source_code):
                animation_count += 1

        # Consider the implementation valid if it uses at least 2 different animation types
        assert animation_count >= 2, f"{impl_name} should use various animations like Transform, Write, FadeIn, FadeOut"
    except (AttributeError, TypeError):
        pytest.skip(f"{impl_name} can't access construct method source")

import pytest
import os
import sys
import json
from typing import Dict, List, Optional, Any

# Import from local test_utils.py in the same directory
from test_utils import TestUtils, TestResultsManager

# Load all implementations in the current sandbox
implementations = TestUtils.load_all_implementations()
test_results = TestResultsManager()

@pytest.fixture(scope="session")
def sandbox_dir():
    """Fixture to provide the sandbox directory path."""
    return os.path.dirname(os.path.abspath(__file__))

@pytest.fixture(scope="session")
def sandbox_name():
    """Fixture to provide the sandbox name."""
    return os.path.basename(os.path.dirname(os.path.abspath(__file__)))

@pytest.fixture(scope="session")
def all_implementations():
    """Fixture to provide all implementations as a dictionary."""
    return implementations

@pytest.fixture(params=list(implementations.items()))
def implementation(request):
    """Fixture to provide each implementation to tests one at a time."""
    return request.param

@pytest.fixture(scope="session")
def results_manager():
    """Fixture to provide access to the test results manager."""
    return test_results

# Hook for collecting test results
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
    """Pytest hook to collect test results."""
    # Execute all other hooks to obtain the report object
    outcome = yield
    rep = outcome.get_result()

    # We're only interested in the call outcome
    if rep.when == "call":
        if hasattr(item, "callspec") and "implementation" in item.callspec.params:
            # Get implementation name and module
            impl_name, _ = item.callspec.params["implementation"]

            # Get test name
            test_name = item.nodeid.split("::")[-1]

            # Record result
            if rep.passed:
                test_results.record_result(impl_name, test_name, True)
            elif rep.failed:
                error_msg = str(rep.longrepr) if rep.longrepr else "Test failed"
                test_results.record_result(impl_name, test_name, False, error_msg)
            elif rep.skipped:
                skip_reason = rep.longrepr[2] if rep.longrepr else "Test skipped"
                test_results.record_skip(impl_name, test_name, skip_reason)

# Hook to save results at the end of testing
@pytest.hookimpl(trylast=True)
def pytest_sessionfinish(session, exitstatus):
    """Save test results at the end of the test session."""
    test_results.save_results()

import os
import sys
import glob
import re
import importlib.util
import traceback
import types
from typing import Dict, List, Optional, Any, Tuple

class TestUtils:
    @staticmethod
    def discover_implementation_files(directory: str = None) -> List[str]:
        """Find all implementation files in the current sandbox directory."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        patterns = [
            r'modified_code\d+\.py',
            r'new_code\d+\.py',
            # r'original_code\.py',
            r'implementation\d*\.py'
        ]

        pattern = re.compile('|'.join(f'({p})' for p in patterns))
        implementations = []

        for file_path in glob.glob(os.path.join(directory, '*.py')):
            if pattern.search(os.path.basename(file_path)):
                implementations.append(file_path)

        # Sort files numerically
        def sort_key(path):
            filename = os.path.basename(path)
            match = re.search(r'(\d+)', filename)
            return int(match.group(1)) if match else 0

        return sorted(implementations, key=sort_key)

    @staticmethod
    def create_mock_module(file_path: str, module_name: str, error_info: str) -> types.ModuleType:
        """Create a mock module that contains error information but can still be tested."""
        # Create a new module object
        mock_module = types.ModuleType(module_name)

        # Add basic attributes
        mock_module.__file__ = file_path
        mock_module.__name__ = module_name
        mock_module.__display_name__ = module_name
        mock_module.__error__ = error_info

        # Add a dummy function that can be detected by test functions
        def dummy_function(*args, **kwargs):
            return f"Error in module: {error_info}"

        setattr(mock_module, "implementation_error", dummy_function)

        return mock_module

    @staticmethod
    def load_module(file_path: str, module_name: Optional[str] = None) -> Any:
        """
        Safely load a module from a file path with proper error handling.
        If the module has errors, return a mock module that can still be tested.
        """
        if module_name is None:
            module_name = os.path.basename(file_path).replace('.py', '')

        # Create a unique module name to avoid conflicts
        sandbox_id = os.path.basename(os.path.dirname(file_path))
        unique_module_name = f"{sandbox_id}_{module_name}"

        try:
            # First, try to read the file to check for syntax errors
            with open(file_path, 'r') as f:
                source_code = f.read()

            # Check for syntax errors by compiling the code
            try:
                compiled = compile(source_code, file_path, 'exec')
            except SyntaxError as e:
                error_msg = f"Syntax error: {str(e)}"
                print(f"Syntax error in {file_path}: {e}")
                print(f"  Line {e.lineno}, column {e.offset}: {e.text}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module spec
            spec = importlib.util.spec_from_file_location(unique_module_name, file_path)
            if spec is None or spec.loader is None:
                error_msg = f"Could not create spec for {file_path}"
                print(f"Error: {error_msg}")
                return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

            # Create the module object
            module = importlib.util.module_from_spec(spec)
            sys.modules[unique_module_name] = module

            # Special handling for execution errors
            try:
                # Execute the module code in a safe way
                spec.loader.exec_module(module)
                # Store the original name for reference
                module.__display_name__ = module_name
                return module
            except Exception as e:
                error_msg = f"Runtime error: {str(e)}"
                traceback_str = traceback.format_exc()
                print(f"Error executing module {file_path}: {e}")
                print(traceback_str)

                # Create a partial module that contains what we loaded before the error
                mock_module = TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

                # Copy any attributes that might have been defined before the error
                for attr_name in dir(module):
                    if not attr_name.startswith('__'):
                        try:
                            setattr(mock_module, attr_name, getattr(module, attr_name))
                        except Exception:
                            pass  # Skip attributes that can't be copied

                return mock_module

        except FileNotFoundError as e:
            error_msg = f"File not found: {str(e)}"
            print(f"Error: {error_msg}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)
        except Exception as e:
            error_msg = f"Unexpected error: {str(e)}"
            print(f"Error loading module {file_path}: {e}")
            return TestUtils.create_mock_module(file_path, unique_module_name, error_msg)

    @classmethod
    def load_all_implementations(cls, directory: str = None) -> Dict[str, Any]:
        """Load all implementation files in the directory, including those with errors."""
        if directory is None:
            directory = os.path.dirname(os.path.abspath(__file__))

        implementations = {}

        implementation_files = cls.discover_implementation_files(directory)
        if not implementation_files:
            print("WARNING: No implementation files found. Check your file naming patterns.")

        for file_path in implementation_files:
            module_name = os.path.basename(file_path).replace('.py', '')
            module = cls.load_module(file_path, module_name)

            # Always add the module, even if it has errors
            implementations[module_name] = module

            if hasattr(module, '__error__'):
                print(f"Loaded with errors: {module_name} - {module.__error__}")
            else:
                print(f"Successfully loaded: {module_name}")

        return implementations

class TestResultsManager:
    def __init__(self):
        self.results = {}
        self.sandbox_name = os.path.basename(os.path.dirname(os.path.abspath(__file__)))

    def record_result(self, impl_name: str, test_name: str, passed: bool,
                     error_msg: Optional[str] = None) -> None:
        """Record a test result for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        if passed:
            self.results[impl_name]["passed"] += 1
        else:
            self.results[impl_name]["failed"] += 1
            if error_msg:
                self.results[impl_name]["errors"].append({
                    "test": test_name,
                    "error": error_msg
                })

    def record_skip(self, impl_name: str, test_name: str,
                   reason: Optional[str] = None) -> None:
        """Record a skipped test for an implementation."""
        if impl_name not in self.results:
            self.results[impl_name] = {"passed": 0, "failed": 0, "skipped": 0, "errors": []}

        self.results[impl_name]["skipped"] += 1
        if reason:
            self.results[impl_name]["errors"].append({
                "test": test_name,
                "error": f"SKIPPED: {reason}"
            })

    def get_winner(self) -> Tuple[Optional[int], Dict]:
        """Determine the winner based on test results."""
        winner = None
        max_passed = -1

        for impl_name, results in self.results.items():
            if impl_name == "original_code":
                continue  # Skip original code when determining winner

            if results["passed"] > max_passed:
                max_passed = results["passed"]
                winner = impl_name
            # Break ties by looking at failure count
            elif results["passed"] == max_passed and winner is not None:
                if results["failed"] < self.results[winner]["failed"]:
                    winner = impl_name

        # Convert winner to numeric index if possible
        winner_index = -1
        if winner and re.match(r'modified_code\d+', winner):
            try:
                winner_index = int(re.search(r'(\d+)', winner).group(1))
            except (AttributeError, ValueError):
                pass

        return winner_index, self.results

    def save_results(self, filename: str = "test_results.json") -> None:
        """Save test results to a JSON file."""
        import json

        winner_index, results = self.get_winner()

        # Check if all tests were skipped
        all_skipped = all(
            stats["skipped"] == stats["passed"] + stats["failed"] + stats["skipped"]
            for impl_name, stats in results.items()
            if impl_name != "original_code"
        )

        output = {
            "winner": winner_index,
            "all_skipped": all_skipped,
            "results": {
                name: {
                    "passed": stats["passed"],
                    "failed": stats["failed"],
                    "skipped": stats["skipped"],
                    "total": stats["passed"] + stats["failed"] + stats["skipped"]
                }
                for name, stats in results.items()
                if not name.startswith("_")  # Skip internal items
            }
        }

        with open(filename, "w") as f:
            json.dump(output, f, indent=2)

        print(f"Test results saved to {filename}")

        return output