Skip to content

Commit

Permalink
added all tests to start with a full coverage.
Browse files Browse the repository at this point in the history
  • Loading branch information
darius BERNARD committed Feb 3, 2017
1 parent 4612db6 commit 4be2ca7
Show file tree
Hide file tree
Showing 11 changed files with 144 additions and 66 deletions.
14 changes: 14 additions & 0 deletions dynamic_logging/admin.py
Original file line number Diff line number Diff line change
@@ -1 +1,15 @@
# -*- coding: utf-8 -*-


from django.contrib import admin
from .models import Config, Trigger
import dynamic_logging.views

@admin.register(Config)
class ConfigAdmin(admin.ModelAdmin):
pass


@admin.register(Trigger)
class TriggerAdmin(admin.ModelAdmin):
pass
2 changes: 1 addition & 1 deletion dynamic_logging/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def ready(self):
from dynamic_logging.scheduler import main_scheduler
try:
main_scheduler.reload()
except OperationalError:
except OperationalError: # pragma: nocover
pass # no trigger table exists atm. we don't care since there is no Trigger to pull.
# setup signals for Trigger changes. it will reload the current trigger and next one
from .signals import reload_timers_on_trigger_change
Expand Down
19 changes: 17 additions & 2 deletions dynamic_logging/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,29 @@
from __future__ import absolute_import, print_function, unicode_literals

import logging
import threading
from collections import defaultdict
from contextlib import contextmanager
from functools import partial

logger = logging.getLogger(__name__)


class MockHandler(logging.Handler):
"""Mock logging handler to check for expected logs."""
messages = defaultdict(list)

_messages_by_thread = defaultdict(list)

@classmethod
@contextmanager
def capture(cls):
current = defaultdict(list)
cls._messages_by_thread[threading.current_thread()].append(current)
try:
yield current
finally:
cls._messages_by_thread[threading.current_thread()].remove(current)

def emit(self, record):
self.messages[record.levelname.lower()].append(record.getMessage())
for messages_list in self._messages_by_thread[threading.current_thread()]:
messages_list[record.levelname.lower()].append(record.getMessage())
12 changes: 6 additions & 6 deletions dynamic_logging/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals

import django.utils.timezone
from django.db import migrations, models

import dynamic_logging.models
import django.utils.timezone


class Migration(migrations.Migration):
Expand All @@ -16,18 +15,19 @@ class Migration(migrations.Migration):
migrations.CreateModel(
name='Config',
fields=[
('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('config_json', models.TextField()),
],
),
migrations.CreateModel(
name='Trigger',
fields=[
('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)),
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('name', models.CharField(max_length=255)),
('start_date', models.DateTimeField(null=True, default=django.utils.timezone.now)),
('end_date', models.DateTimeField(null=True, default=dynamic_logging.models.now_plus_2hours)),
('is_active', models.BooleanField(default=True)),
('start_date', models.DateTimeField(default=django.utils.timezone.now, null=True)),
('end_date', models.DateTimeField(default=dynamic_logging.models.now_plus_2hours, null=True)),
('config', models.ForeignKey(to='dynamic_logging.Config', related_name='triggers')),
],
options={
Expand Down
11 changes: 0 additions & 11 deletions dynamic_logging/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,6 @@ def default(cls):
def __str__(self):
return 'trigger %s from %s to %s for config %s' % (self.name, self.start_date, self.end_date, self.config.name)

def is_active(self, date=None):
"""
return True if the current trigger is active at the given date. if no date is given, current date is used
:param date: the date to check or None for now()
:return:
:rtype: bool
"""
date = date or timezone.now()
start_date, end_date = self.start_date, self.end_date
return (start_date <= date or end_date is None) and (end_date >= date or end_date is None)

def apply(self):
self.config.apply(self)

Expand Down
21 changes: 17 additions & 4 deletions dynamic_logging/scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def get_next_wake(current=None, after=None):
def set_next_wake(self, trigger, at):
logger.debug("next trigger to enable : %s at %s", trigger, at, extra={'next_date': at})
with self._lock:
self.reset()
self.reset_timer()
interval = (at - timezone.now()).total_seconds()
self.next_timer = threading.Timer(interval,
functools.partial(self.wake, trigger=trigger, date=at))
Expand All @@ -127,11 +127,23 @@ def set_next_wake(self, trigger, at):
self.next_timer.start()

def reset(self):
"""
reset the logging to the default settings. disable the timer to change it
:return:
"""
with self._lock:
self.reset_timer()
self.current_trigger = Trigger.default()

def reset_timer(self):
"""
reset the timer
:return:
"""
with self._lock:
if self.next_timer is not None:
self.next_timer.cancel()
self.next_timer = None
self.current_trigger = Trigger.default()

def activate_current(self):
"""
Expand All @@ -154,7 +166,7 @@ def reload(self):
"""
if self._enabled:
with self._lock:
self.reset()
self.reset_timer()
current = self.activate_current()
trigger, at = self.get_next_wake(current=current)
if at:
Expand All @@ -178,7 +190,8 @@ def wake(self, trigger, date):
# get the next trigger valid at the current expected date
# we don't use timezone.now() to prevent the case where threading.Timer wakeup some ms befor the expected
# date
self.set_next_wake(next_trigger, at)
if at:
self.set_next_wake(next_trigger, at)

def apply(self, trigger):
if self.current_trigger != trigger:
Expand Down
60 changes: 35 additions & 25 deletions dynamic_logging/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ def get_tz_date(dmy):
class SchedulerTest(TestCase):
def setUp(self):
main_scheduler.disable()
assert not main_scheduler.is_enabled()
c = Config.objects.create(name='nothing logged')
dates = [
# # # | jan | fevrier
Expand Down Expand Up @@ -182,15 +183,22 @@ def test_auto_reload_on_trigger_changes(self):
t.delete()
self.assertIsNone(main_scheduler.next_timer)

def test_wake(self):
now_plus_2h = timezone.now() + datetime.timedelta(hours=2)
now_plus_4h = timezone.now() + datetime.timedelta(hours=4)
# no trigger in fixtures
self.assertIsNone(main_scheduler.next_timer)
t = Trigger.objects.create(name='fake', config=self.config, start_date=now_plus_2h, end_date=now_plus_4h)

self.assertEqual(main_scheduler.current_trigger, Trigger.default())
main_scheduler.wake(t, now_plus_2h)
self.assertEqual(main_scheduler.current_trigger, t)

class ConfigApplyTest(TestCase):
def setUp(self):
MockHandler.messages.clear()

class ConfigApplyTest(TestCase):
def tearDown(self):
# reset the default config
Config.default().apply()
MockHandler.messages.clear()

def test_default_config_by_default(self):
loggers = get_loggers()
Expand Down Expand Up @@ -242,11 +250,12 @@ def test_level_config(self):
self.assertEqual(logger.handlers, [])

def test_messages_passed(self):
self.assertEqual(MockHandler.messages['debug'], [])
logger = logging.getLogger('testproject.testapp')
logger.debug("couocu")
# handler not attached to this logger
self.assertEqual(MockHandler.messages['debug'], [])
with MockHandler.capture() as messages:
self.assertEqual(messages['debug'], [])
logger = logging.getLogger('testproject.testapp')
logger.debug("couocu")
# handler not attached to this logger
self.assertEqual(messages['debug'], [])
# setup new config
cfg = Config(name='empty')
cfg.config = {"loggers": {
Expand All @@ -262,11 +271,12 @@ def test_messages_passed(self):
cfg.apply()
# log debug ineficient
logger.debug("couocu")
self.assertEqual(MockHandler.messages['debug'], [])
self.assertEqual(MockHandler.messages['warning'], [])
logger.warn("hey")
self.assertEqual(MockHandler.messages['debug'], [])
self.assertEqual(MockHandler.messages['warning'], ['hey'])
with MockHandler.capture() as messages:
self.assertEqual(messages['debug'], [])
self.assertEqual(messages['warning'], [])
logger.warn("hey")
self.assertEqual(messages['debug'], [])
self.assertEqual(messages['warning'], ['hey'])

def test_config_reversed(self):
logger = logging.getLogger('testproject.testapp')
Expand All @@ -285,14 +295,14 @@ def test_config_reversed(self):
}}
cfg.apply()
# log debug ineficient
self.assertEqual(MockHandler.messages['warning'], [])

logger.warn("hey")
self.assertEqual(MockHandler.messages['warning'], ['hey'])
logger.warn("hey")
self.assertEqual(MockHandler.messages['warning'], ['hey', 'hey'])

# default config does not add to mockhandler
Config.default().apply()
logger.warn("hey")
self.assertEqual(MockHandler.messages['warning'], ['hey', 'hey'])
with MockHandler.capture() as messages:
logger.warn("hey")
self.assertEqual(messages['warning'], ['hey'])
logger.warn("hey")
self.assertEqual(messages['warning'], ['hey', 'hey'])

with MockHandler.capture() as messages:
# default config does not add to mockhandler
Config.default().apply()
logger.warn("hey")
self.assertEqual(messages['warning'], [])
4 changes: 3 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ description-file = README.rst
universal = 1

[coverage:run]
omit = docs
include =
dynamic_logging
testproject

[isort]
line_length=119
Expand Down
47 changes: 47 additions & 0 deletions testproject/testapp/tests.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
from django.contrib.auth import get_user_model
from django.test import TestCase


# Create your tests here.
from dynamic_logging.handlers import MockHandler
from dynamic_logging.models import Config


class TestPages(TestCase):
def setUp(self):
pass

def test_200(self):
res = self.client.get('/testapp/ok/')
Expand All @@ -13,3 +18,45 @@ def test_200(self):
def test_401(self):
res = self.client.get('/testapp/error401/')
self.assertEqual(res.status_code, 401)

def test_raise(self):
self.assertRaises(Exception, self.client.get, '/testapp/error500/')

def test_log_no_cfg(self):
with MockHandler.capture() as messages:
res = self.client.get('/testapp/log/DEBUG/testproject.testapp/')
self.assertEqual(res.status_code, 200)
self.assertEqual(messages['debug'], [])

def test_log_with_cfg(self):
cfg = Config(name='mocklog')
cfg.config = {"loggers": {
"testproject.testapp": {
"handlers": ["mock"],
"level": "WARN",
}
}}
cfg.apply()
with MockHandler.capture() as messages:
res = self.client.get('/testapp/log/DEBUG/testproject.testapp/')
self.assertEqual(res.status_code, 200)
self.assertEqual(messages['debug'], [])

def test_log_bad_level(self):
self.assertRaises(Exception, self.client.get, '/testapp/log/OOPS/testproject.testapp/')


class TestAdminContent(TestCase):

def setUp(self):
super(TestAdminContent, self).setUp()
u = get_user_model().objects.create(username='admin', is_staff=True, is_superuser=True)
""":type: django.contrib.auth.models.User"""
u.set_password('password')
u.save()
self.client.login(username='admin', password='password')

def test_logging_in_admin(self):
response = self.client.get('/admin/')
self.assertContains(response, 'Trigger')

4 changes: 4 additions & 0 deletions testproject/testapp/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import logging

from django.forms.forms import Form
from django.http.response import HttpResponse
from django.shortcuts import render


# Create your views here.
from django.views.generic.edit import FormView


def error401(request):
return HttpResponse(status=401)
Expand All @@ -26,3 +29,4 @@ def log_somthing(request, level='debug', loggername=__name__):
logger = logging.getLogger(loggername)
logger.log(level, "message from view", extra={'level': level, 'loggername': loggername})
return HttpResponse("ok. logged to %s" % loggername)

16 changes: 0 additions & 16 deletions testproject/wsgi.py

This file was deleted.

0 comments on commit 4be2ca7

Please sign in to comment.