Skip to content

Commit

Permalink
Merge pull request #446 from candango/develop
Browse files Browse the repository at this point in the history
build: merge develop to master
  • Loading branch information
piraz committed Feb 24, 2024
2 parents 889fa8a + 649f6ef commit cb053b6
Show file tree
Hide file tree
Showing 20 changed files with 263 additions and 153 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/run_tests.yml
Expand Up @@ -11,9 +11,9 @@ jobs:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
4 changes: 2 additions & 2 deletions docs/conf.py
Expand Up @@ -47,7 +47,7 @@

# General information about the project.
project = u'Firenado Framework'
copyright = u'2015-2023, Flavio Garcia'
copyright = u'2015-2024, Flavio Garcia'
author = u'Flavio Garcia'

# The version info for the project you're documenting, acts as replacement for
Expand All @@ -57,7 +57,7 @@
# The short X.Y version.
version = '0.9'
# The full version, including alpha/beta/rc tags.
release = '0.9.3'
release = '0.9.4'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
Expand Down
4 changes: 2 additions & 2 deletions docs/releases/v0.9.3.rst
@@ -1,10 +1,10 @@
What's new in Firenado 0.9.1
What's new in Firenado 0.9.3
============================

Nov 25, 2023
------------

We are pleased to announce the release of Firenado 0.9.1.
We are pleased to announce the release of Firenado 0.9.3.

This release updates pexpect to 4.9.0 and removes the monkey patch from the
launcher.
Expand Down
17 changes: 17 additions & 0 deletions docs/releases/v0.9.4.rst
@@ -0,0 +1,17 @@
What's new in Firenado 0.9.4
============================

Feb 23, 2024
------------

We are pleased to announce the release of Firenado 0.9.4.

This release adds proper testers to the Firenado framework.

Here are the highlights:

Features
~~~~~~~~

* Create a TornadoLoader test case `#444 <https://github.com/candango/firenado/issues/444>`_
* Create a ProcessLoader test case `#445 <https://github.com/candango/firenado/issues/445>`_
4 changes: 2 additions & 2 deletions firenado/__init__.py
@@ -1,6 +1,6 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2023 Flavio Garcia
# Copyright 2015-2024 Flavio Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -18,7 +18,7 @@
"""The Firenado Framework"""

__author__ = "Flavio Garcia <piraz@candango.org>"
__version__ = (0, 9, 3)
__version__ = (0, 9, 4)
__licence__ = "Apache License V2.0"


Expand Down
28 changes: 15 additions & 13 deletions firenado/launcher.py
@@ -1,4 +1,4 @@
# Copyright 2015-2023 Flavio Garcia
# Copyright 2015-2024 Flavio Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -56,13 +56,16 @@ def __init__(self, **settings):
os.chdir(self.dir)
if self.app is not None or self.dir is not None:
reload(firenado.conf)
self.configure_logging()

def configure_logging(self, **kargs):
format = kargs.get("format", firenado.conf.log['format'])
level = kargs.get("level", firenado.conf.log['level'])
# Set logging basic configurations
for handler in logging.root.handlers[:]:
# clearing loggers, solution from: https://bit.ly/2yTchyx
logging.root.removeHandler(handler)
logging.basicConfig(level=firenado.conf.log['level'],
format=firenado.conf.log['format'])
logging.basicConfig(level=level, format=format)

def load(self):
raise NotImplementedError()
Expand Down Expand Up @@ -152,16 +155,15 @@ def is_alive(self):
class TornadoLauncher(FirenadoLauncher):

def __init__(self, **settings):
super(TornadoLauncher, self).__init__(**settings)
super().__init__(**settings)
self.http_server = None
self.application = None
self.MAX_WAIT_SECONDS_BEFORE_SHUTDOWN = firenado.conf.app[
'wait_before_shutdown']
if self.addresses is None or self.addresses == ['']:
if firenado.conf.app['addresses']:
self.addresses = firenado.conf.app['addresses']
else:
self.addresses = firenado.conf.app['default_addresses']
self.addresses = firenado.conf.app['default_addresses']
if ((self.addresses is None or self.addresses == ['']) and
firenado.conf.app['addresses']):
self.addresses = firenado.conf.app['addresses']
if self.port is None:
self.port = firenado.conf.app['port']

Expand All @@ -181,13 +183,13 @@ def launch(self):
if os.name == "posix":
signal.signal(signal.SIGTSTP, self.sig_handler)
self.http_server = tornado.httpserver.HTTPServer(self.application)
if firenado.conf.app['xheaders'] is not None and type(
firenado.conf.app['xheaders']) == bool:
if firenado.conf.app['xheaders'] is not None and isinstance(
firenado.conf.app['xheaders'], bool):
logger.debug("Setting http server xheaders as %s.",
firenado.conf.app['xheaders'])
self.http_server.xheaders = firenado.conf.app['xheaders']
if firenado.conf.app['xheaders'] is not None and type(
firenado.conf.app['xheaders']) != bool:
if firenado.conf.app['xheaders'] is not None and isinstance(
firenado.conf.app['xheaders'], bool):
logger.warning("The xheaders defined in the application section"
"must be bool instead of %s. Ignoring the "
"configuration item.",
Expand Down
18 changes: 8 additions & 10 deletions firenado/schedule.py
@@ -1,6 +1,4 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2023 Flavio Garcia
# Copyright 2015-2024 Flavio Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -42,8 +40,8 @@


def next_from_cron(cron: str) -> datetime:
""" Return a datatetime object with the next execution based on the informed
cron string and the current time.
""" Return a datatetime object with the next execution based on the
informed cron string and the current time.
:param str cron: The cron string
:return datetime: A datetime object with the next execution
Expand Down Expand Up @@ -121,7 +119,7 @@ def remove_job(self, job_id):
job = self.get_job(job_id)
if job is None:
return None
del(self._jobs[job_id])
del self._jobs[job_id]
return job.id

def run(self):
Expand All @@ -135,8 +133,8 @@ def run(self):
def _manage_jobs(self):
logger.debug("Scheduler [id: %s, name: %s] managing jobs.", self.id,
self.name)
logger.debug("Scheduler [id: %s, name: %s] stopping periodic callback."
, self.id, self.name)
logger.debug("Scheduler [id: %s, name: %s] stopping periodic "
"callback.", self.id, self.name)
self._periodic_callback.stop()
for job in self.jobs:
if not job.already_scheduled:
Expand All @@ -154,8 +152,8 @@ def _manage_jobs(self):

logger.debug("Scheduler [id: %s, name: %s] ending of managing jobs.",
self.id, self.name)
logger.debug("Scheduler [id: %s, name: %s] starting periodic callback."
, self.id, self.name)
logger.debug("Scheduler [id: %s, name: %s] starting periodic "
"callback.", self.id, self.name)
self._periodic_callback.start()


Expand Down
9 changes: 4 additions & 5 deletions firenado/security.py
@@ -1,6 +1,4 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2023 Flavio Garcia
# Copyright 2015-2024 Flavio Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -122,8 +120,9 @@ def default_class_authentication(self: tornadoweb.TornadoHandler):
import firenado.conf
if "login" in firenado.conf.app:
warnings.warn("The \"login\" configuration in the application %s is"
"depreciated. Please replace the configuration app.login"
"to app.security.auth instead.", DeprecationWarning, 2)
"depreciated. Please replace the configuration "
"app.login to app.security.auth instead.",
DeprecationWarning, 2)
login_urls = self.get_rooted_path(
firenado.conf.app['login']['urls']['default']
)
Expand Down
123 changes: 39 additions & 84 deletions firenado/testing.py
@@ -1,6 +1,4 @@
# -*- coding: UTF-8 -*-
#
# Copyright 2015-2023 Flavio Garcia
# Copyright 2015-2024 Flavio Garcia
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,14 +13,9 @@
# limitations under the License.

import asyncio
from behave.api.async_step import async_run_until_complete
from firenado.launcher import ProcessLauncher
from firenado.launcher import ProcessLauncher, TornadoLauncher
from tornado.testing import (bind_unused_port, AsyncTestCase,
AsyncHTTPTestCase)
from tornado.httpclient import HTTPResponse
from tornado import gen, ioloop
from typing import Any
import warnings


def get_event_loop():
Expand All @@ -35,93 +28,55 @@ def get_event_loop():
return loop if loop else asyncio.new_event_loop()


class ProcessLauncherTestCase(AsyncHTTPTestCase):
class TornadoAsyncTestCase(AsyncTestCase):

@property
def launcher(self):
def launcher(self) -> ProcessLauncher:
return self.__launcher

def get_launcher(self) -> ProcessLauncher:
"""Should be overridden by subclasses to return a
`firenado.launcher.ProcessLauncher`.
`firenado.launcher.TornadoLauncher`.
"""
raise NotImplementedError()

def get_http_port(self) -> int:
"""Returns the port used by the server.
A new port is chosen for each test.
"""
def http_port(self) -> int:
return self.__port

@async_run_until_complete(should_close=False, loop=get_event_loop())
async def setUp(self) -> None:
self.should_close_asyncio_loop = False
with warnings.catch_warnings():
warnings.simplefilter("ignore")
sock, port = bind_unused_port()
sock.close()
self.__port = port
self.__launcher = self.get_launcher()
self.__launcher.port = self.__port
self.__launcher.load()
self.http_client = self.get_http_client()
self.io_loop = ioloop.IOLoop.current()
await self.__launcher.launch()
await gen.sleep(1)

def get_url(self, path: str) -> str:
"""Returns an absolute url for the given path on the test server."""
return "%s://127.0.0.1:%s%s" % (self.get_protocol(),
self.get_http_port(), path)

async def fetch(
self, path: str, raise_error: bool = False, **kwargs: Any
) -> HTTPResponse:
"""Convenience method to synchronously fetch a URL.
The given path will be appended to the local server's host and
port. Any additional keyword arguments will be passed directly to
`.AsyncHTTPClient.fetch` (and so could be used to pass
``method="POST"``, ``body="..."``, etc).
If the path begins with http:// or https://, it will be treated as a
full URL and will be fetched as-is.
If ``raise_error`` is ``True``, a `tornado.httpclient.HTTPError` will
be raised if the response code is not 200. This is the same behavior
as the ``raise_error`` argument to `.AsyncHTTPClient.fetch`, but
the default is ``False`` here (it's ``True`` in `.AsyncHTTPClient`)
because tests often need to deal with non-200 response codes.
.. versionchanged:: 5.0
Added support for absolute URLs.
.. versionchanged:: 5.1
Added the ``raise_error`` argument.
.. deprecated:: 5.1
This method currently turns any exception into an
`.HTTPResponse` with status code 599. In Tornado 6.0,
errors other than `tornado.httpclient.HTTPError` will be
passed through, and ``raise_error=False`` will only
suppress errors that would be raised due to non-200
response codes.
def setUp(self) -> None:
import logging
super().setUp()
sock, port = bind_unused_port()
sock.close()
self.__port = port
self.__launcher = self.get_launcher()
self.__launcher.configure_logging(level=logging.ERROR)
self.__launcher.port = self.__port
self.__launcher.load()
asyncio.run(self.__launcher.launch())

def tearDown(self) -> None:
self.__launcher.shutdown()
super().tearDown()


class TornadoAsyncHTTPTestCase(AsyncHTTPTestCase):

def get_launcher(self) -> TornadoLauncher:
"""Should be overridden by subclasses to return a
`firenado.launcher.TornadoLauncher`.
"""
if path.lower().startswith(("http://", "https://")):
url = path
else:
url = self.get_url(path)
raise NotImplementedError()

return await self.http_client.fetch(url, raise_error=raise_error,
**kwargs)
def get_log_level(self):
return None

def tearDown(self) -> None:
with warnings.catch_warnings():
warnings.simplefilter("ignore")
self.http_client.close()
AsyncTestCase.tearDown(self)
self.__launcher.shutdown()
def get_app(self):
import logging
launcher = self.get_launcher()
launcher.load()
if self.get_log_level() is not None:
launcher.configure_logging(level=self.get_log_level())
else:
launcher.configure_logging(level=logging.WARN)
return launcher.application
4 changes: 2 additions & 2 deletions requirements/basic.txt
@@ -1,3 +1,3 @@
cartola>=0.17
cartola>=0.18
taskio==0.0.6
tornado==6.3.3
tornado==6.4
2 changes: 1 addition & 1 deletion requirements/redis.txt
@@ -1,2 +1,2 @@
redis>=5.0.1
hiredis>=2.2.3
hiredis>=2.3.2
2 changes: 1 addition & 1 deletion requirements/tests.txt
@@ -1,3 +1,3 @@
behave==1.2.6
bandit>=1.7.0
pymysql==1.0.2
pymysql==1.1.0
2 changes: 1 addition & 1 deletion tests/features/steps/launcher.py
Expand Up @@ -43,7 +43,7 @@ async def step_application_running_correctly_at_port(context, port):
print("Error: %s" % e)
context.tester.assertTrue(False)
else:
context.tester.assertEqual(b"IndexHandler output", response.body)
context.tester.assertEqual(b"Get output", response.body)
context.tester.assertTrue(context.launcher.is_alive())


Expand Down

0 comments on commit cb053b6

Please sign in to comment.