#!/usr/bin/env python # Copyright 2023 The Servo Project Developers. See the COPYRIGHT # file at the top-level directory of this distribution. # # Licensed under the Apache License, Version 2.0 or the MIT license # , at your # option. This file may not be copied, modified, or distributed # except according to those terms. # pylint: disable=broad-except # pylint: disable=dangerous-default-value # pylint: disable=global-statement # pylint: disable=line-too-long # pylint: disable=missing-docstring # pylint: disable=protected-access # This allows using types that are defined later in the file. from __future__ import annotations import dataclasses import json import locale import logging import os import shutil import subprocess import tempfile import threading import time import unittest from functools import partial from typing import Any, Optional, Tuple, Type from wsgiref.simple_server import WSGIRequestHandler, make_server import flask import flask.cli import requests from .exporter import SyncRun, WPTSync from .exporter.step import CreateOrUpdateBranchForPRStep TESTS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tests") SYNC: Optional[WPTSync] = None TMP_DIR: Optional[str] = None PORT = 9000 @dataclasses.dataclass class MockPullRequest(): head: str number: int state: str = "open" class MockGitHubAPIServer(): def __init__(self, port: int): self.port = port self.disable_logging() self.app = flask.Flask(__name__) self.pulls: list[MockPullRequest] = [] class NoLoggingHandler(WSGIRequestHandler): def log_message(self, *args): pass if logging.getLogger().level == logging.DEBUG: handler = WSGIRequestHandler else: handler = NoLoggingHandler self.server = make_server('localhost', self.port, self.app, handler_class=handler) self.start_server_thread() def disable_logging(self): flask.cli.show_server_banner = lambda *args: None logging.getLogger("werkzeug").disabled = True logging.getLogger('werkzeug').setLevel(logging.CRITICAL) def start(self): self.thread.start() # Wait for the server to be started. while True: try: response = requests.get(f'http://localhost:{self.port}/ping', timeout=1) assert response.status_code == 200 assert response.text == 'pong' break except Exception: time.sleep(0.1) def reset_server_state_with_pull_requests(self, pulls: list[MockPullRequest]): response = requests.get( f'http://localhost:{self.port}/reset-mock-github', json=[dataclasses.asdict(pull_request) for pull_request in pulls], timeout=1 ) assert response.status_code == 200 assert response.text == 'πŸ‘' def shutdown(self): self.server.shutdown() self.thread.join() def start_server_thread(self): # pylint: disable=unused-argument self.thread = threading.Thread(target=self.server.serve_forever, daemon=True) self.thread.start() @self.app.route("/ping") def ping(): return ('pong', 200) @self.app.route("/reset-mock-github") def reset_server(): self.pulls = [ MockPullRequest(pull_request['head'], pull_request['number'], pull_request['state']) for pull_request in flask.request.json] return ('πŸ‘', 200) @self.app.route("/repos///pulls//merge", methods=['PUT']) def merge_pull_request(org, repo, number): for pull_request in self.pulls: if pull_request.number == number: pull_request.state = 'closed' return ('', 204) return ('', 404) @self.app.route("/search/issues", methods=['GET']) def search(): params = {} param_strings = flask.request.args.get("q", "").split(" ") for string in param_strings: parts = string.split(":") params[parts[0]] = parts[1] assert params["is"] == "pr" assert params["state"] == "open" assert "author" in params assert "head" in params head_ref = f"{params['author']}:{params['head']}" for pull_request in self.pulls: if pull_request.head == head_ref: return json.dumps({ "total_count": 1, "items": [{ "number": pull_request.number }] }) return json.dumps({"total_count": 0, "items": []}) @self.app.route("/repos///pulls", methods=['POST']) def create_pull_request(org, repo): new_pr_number = len(self.pulls) + 1 self.pulls.append(MockPullRequest( flask.request.json["head"], new_pr_number, "open" )) return {"number": new_pr_number} @self.app.route("/repos///pulls/", methods=['PATCH']) def update_pull_request(org, repo, number): for pull_request in self.pulls: if pull_request.number == number: if 'state' in flask.request.json: pull_request.state = flask.request.json['state'] return ('', 204) return ('', 404) @self.app.route("/repos///issues//labels", methods=['GET', 'POST']) @self.app.route("/repos///issues//labels/