From 0a47cc0dcd92d2d36c28b3ea9439e42849e0d3eb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 11 Feb 2022 00:31:52 +0530 Subject: [PATCH] fix(test): Make requests to the QSGI app in separate threads Did this to mimic how requests are handled in dev/prod servers - new thread for new request ;) --- frappe/tests/test_api.py | 53 ++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/frappe/tests/test_api.py b/frappe/tests/test_api.py index c7597acb3a..0a4e3e0c4f 100644 --- a/frappe/tests/test_api.py +++ b/frappe/tests/test_api.py @@ -2,14 +2,19 @@ import sys import unittest from contextlib import contextmanager from random import choice -from typing import Dict, Optional +from typing import Dict, Optional, Tuple +from threading import Thread import requests from semantic_version import Version import frappe -from frappe.utils import get_site_url, get_test_client +from frappe.utils import get_site_url +try: + _site = frappe.local.site +except Exception: + _site = None @contextmanager def suppress_stdout(): @@ -21,6 +26,36 @@ def suppress_stdout(): finally: sys.stdout = sys.__stdout__ +class ThreadWithReturnValue(Thread): + def __init__(self, group=None, target=None, name=None, + args=(), kwargs={}, Verbose=None): + Thread.__init__(self, group, target, name, args, kwargs) + self._return = None + + def run(self): + from unittest.mock import patch + if self._target is not None: + with patch("frappe.app.get_site_name", return_value=_site): + # print(self._args, self._kwargs) + self._return = self._target(*self._args, **self._kwargs) + + def join(self, *args): + Thread.join(self, *args) + return self._return + + +def get_test_client(): + from frappe.app import application + from werkzeug.test import Client + + return Client(application) + + +def make_request(target: str, args: Optional[Tuple] = None, kwargs: Optional[Dict] = None): + t = ThreadWithReturnValue(target=target, args=args, kwargs=kwargs) + t.start() + t.join() + return t._return class FrappeAPITestCase(unittest.TestCase): SITE = frappe.local.site @@ -31,24 +66,25 @@ class FrappeAPITestCase(unittest.TestCase): @property def sid(self): if not getattr(self, "_sid", None): - r = self.TEST_CLIENT.post("/api/method/login", data={ + r = self.post("/api/method/login", { "usr": "Administrator", "pwd": frappe.conf.admin_password or "admin", }) self._sid = r.headers[2][1].split(";")[0].lstrip("sid=") + return self._sid def get(self, path: str, params: Optional[Dict] = None): - return self.TEST_CLIENT.get(path, data=params) + return make_request(target=self.TEST_CLIENT.get, args=(path, ), kwargs={"data": params}) def post(self, path, data): - return self.TEST_CLIENT.post(path, data=frappe.as_json(data)) + return make_request(target=self.TEST_CLIENT.post, args=(path, ), kwargs={"data": data}) def put(self, path, data): - return self.TEST_CLIENT.put(path, data=frappe.as_json(data)) + return make_request(target=self.TEST_CLIENT.put, args=(path, ), kwargs={"data": data}) def delete(self, path): - return self.TEST_CLIENT.delete(path) + return make_request(target=self.TEST_CLIENT.delete, args=(path, )) class TestResourceAPI(FrappeAPITestCase): @@ -57,7 +93,6 @@ class TestResourceAPI(FrappeAPITestCase): @classmethod def setUpClass(cls): - frappe.set_user("Administrator") for _ in range(10): doc = frappe.get_doc( {"doctype": "ToDo", "description": frappe.mock("paragraph")} @@ -67,13 +102,11 @@ class TestResourceAPI(FrappeAPITestCase): @classmethod def tearDownClass(cls): - frappe.set_user("Administrator") for name in cls.GENERATED_DOCUMENTS: frappe.delete_doc_if_exists(cls.DOCTYPE, name) frappe.db.commit() def setUp(self): - frappe.set_user("Administrator") # commit to ensure consistency in session (postgres CI randomly fails) if frappe.conf.db_type == "postgres": frappe.db.commit()