130 lines
6.2 KiB
Python
130 lines
6.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import threading
|
|
from unittest.mock import patch
|
|
|
|
from odoo.http import Controller, request, route
|
|
from odoo.tests.common import ChromeBrowser, HttpCase, tagged
|
|
from odoo.tools import config, logging
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestHttpCase(HttpCase):
|
|
|
|
def test_console_error_string(self):
|
|
with self.assertLogs(level='ERROR') as log_catcher:
|
|
with self.assertRaises(AssertionError) as error_catcher:
|
|
code = "console.error('test error','message')"
|
|
with patch('odoo.tests.common.ChromeBrowser.take_screenshot', return_value=None):
|
|
self.browser_js(url_path='about:blank', code=code)
|
|
# second line must contains error message
|
|
self.assertEqual(error_catcher.exception.args[0].splitlines()[-1], "test error message")
|
|
self.assertEqual(len(log_catcher.output), 1)
|
|
self.assertIn('test error message', log_catcher.output[0])
|
|
|
|
def test_console_error_object(self):
|
|
with self.assertLogs(level='ERROR') as log_catcher:
|
|
with self.assertRaises(AssertionError) as error_catcher:
|
|
code = "console.error(TypeError('test error message'))"
|
|
with patch('odoo.tests.common.ChromeBrowser.take_screenshot', return_value=None):
|
|
self.browser_js(url_path='about:blank', code=code)
|
|
# second line must contains error message
|
|
self.assertEqual(error_catcher.exception.args[0].splitlines()[-2:],
|
|
['TypeError: test error message', ' at <anonymous>:1:15'])
|
|
self.assertEqual(len(log_catcher.output), 1)
|
|
self.assertIn('TypeError: test error message\n at <anonymous>:1:15', log_catcher.output[0])
|
|
|
|
def test_console_log_object(self):
|
|
logger = logging.getLogger('odoo')
|
|
level = logger.level
|
|
logger.setLevel(logging.INFO)
|
|
self.addCleanup(logger.setLevel, level)
|
|
|
|
with self.assertLogs() as log_catcher:
|
|
code = "console.log({custom:{1:'test', 2:'a'}, value:1, description:'dummy'});console.log('test successful');"
|
|
self.browser_js(url_path='about:blank', code=code)
|
|
console_log_count = 0
|
|
for log in log_catcher.output:
|
|
if '.browser:' in log:
|
|
text = log.split('.browser:', 1)[1]
|
|
if text == 'test successful':
|
|
continue
|
|
self.assertEqual(text, "Object(custom=Object, value=1, description='dummy')")
|
|
console_log_count += 1
|
|
self.assertEqual(console_log_count, 1)
|
|
|
|
|
|
@tagged('-at_install', 'post_install')
|
|
class TestChromeBrowser(HttpCase):
|
|
def setUp(self):
|
|
super().setUp()
|
|
screencasts_dir = config['screencasts'] or config['screenshots']
|
|
with patch.dict(config.options, {'screencasts': screencasts_dir, 'screenshots': config['screenshots']}):
|
|
self.browser = ChromeBrowser(self)
|
|
self.addCleanup(self.browser.stop)
|
|
|
|
def test_screencasts(self):
|
|
self.browser.start_screencast()
|
|
self.browser.navigate_to('about:blank')
|
|
self.browser._wait_ready()
|
|
code = "setTimeout(() => console.log('test successful'), 2000); setInterval(() => document.body.innerText = (new Date()).getTime(), 100);"
|
|
self.browser._wait_code_ok(code, 10)
|
|
self.browser._save_screencast()
|
|
|
|
|
|
class TestRequestRemaining(HttpCase):
|
|
# This test case tries to reproduce the case where a request is lost between two test and is execute during the secone one.
|
|
#
|
|
# - Test A browser js finishes with a pending request
|
|
# - _wait_remaining_requests misses the request since the thread may not be totally spawned (or correctly named)
|
|
# - Test B starts and a SELECT is executed
|
|
# - The request is executed and makes a concurrent fetchall
|
|
# - The test B tries to fetchall and fails since the cursor is already used by the request
|
|
#
|
|
# Note that similar cases can also consume savepoint, make the main cursor readonly, ...
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.thread_a = None
|
|
# this lock is used to ensure the request is executed after test b starts
|
|
cls.main_lock = threading.Lock()
|
|
cls.main_lock.acquire()
|
|
|
|
def test_requests_a(self):
|
|
class Dummycontroller(Controller):
|
|
@route('/web/concurrent', type='http', auth='public', sitemap=False)
|
|
def wait(c, **params):
|
|
self.assertEqual(request.env.cr.__class__.__name__, 'TestCursor')
|
|
request.env.cr.execute('SELECT 1')
|
|
request.env.cr.fetchall()
|
|
# not that the previous queries are not really needed since the http stack will check the registry
|
|
# but this makes the test more clear and robust
|
|
_logger.info('B finish')
|
|
|
|
self.env.registry.clear_cache('routing')
|
|
self.addCleanup(self.env.registry.clear_cache, 'routing')
|
|
|
|
def late_request_thread():
|
|
# In some rare case the request may arrive after _wait_remaining_requests.
|
|
# this thread is trying to reproduce this case.
|
|
_logger.info('Waiting for B to start')
|
|
if self.main_lock.acquire(timeout=10):
|
|
self.url_open("/web/concurrent", timeout=10)
|
|
else:
|
|
_logger.error('Something went wrong and thread was not able to aquire lock')
|
|
TestRequestRemaining.thread_a = threading.Thread(target=late_request_thread)
|
|
self.thread_a.start()
|
|
|
|
def test_requests_b(self):
|
|
self.env.cr.execute('SELECT 1')
|
|
with self.assertLogs('odoo.tests.common') as lc:
|
|
self.main_lock.release()
|
|
_logger.info('B started, waiting for A to finish')
|
|
self.thread_a.join()
|
|
self.assertEqual(lc.output[0].split(':', 1)[1], 'odoo.tests.common:Request with path /web/concurrent has been ignored during test as it it does not contain the test_cursor cookie or it is expired. (required "/base/tests/test_http_case.py:TestRequestRemaining.test_requests_b", got "/base/tests/test_http_case.py:TestRequestRemaining.test_requests_a")')
|
|
self.env.cr.fetchall()
|