Skip to content

Commit

Permalink
Add: Tests for automation history view
Browse files Browse the repository at this point in the history
Add:		Support for Split/Join in history view
  • Loading branch information
fsbraun committed Dec 10, 2021
1 parent f4b80ae commit e1ec9e8
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 74 deletions.
55 changes: 39 additions & 16 deletions runtests.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
from django.core.management import call_command


def runtests(test_path='automations.tests'):
def runtests(test_path="automations.tests"):
if not settings.configured:
# Choose database for settings
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': ':memory:',
"default": {
"ENGINE": "django.db.backends.sqlite3",
"NAME": ":memory:",
}
}
# test_db = os.environ.get('DB', 'sqlite')
Expand All @@ -37,31 +37,54 @@ def runtests(test_path='automations.tests'):
SECRET_KEY="verysecretkeyfortesting",
DATABASES=DATABASES,
INSTALLED_APPS=(
'django.contrib.contenttypes',
'django.contrib.auth',
'django.contrib.sessions',
'automations',
"django.contrib.contenttypes",
"django.contrib.auth",
"django.contrib.sessions",
"automations",
),
ROOT_URLCONF="automations.urls", # tests override urlconf, but it still needs to be defined
LANGUAGES=(
('en', 'English'),
('de', 'Deutsch'),

("en", "English"),
("de", "Deutsch"),
),
MIDDLEWARE=(
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
),
MIDDLEWARE_CLASSES=(),
TEMPLATES=[
{
"BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": (
"src/automations/templates",
# insert your TEMPLATE_DIRS here
),
"OPTIONS": {
"context_processors": (
"django.contrib.auth.context_processors.auth",
"django.template.context_processors.debug",
"django.template.context_processors.i18n",
"django.template.context_processors.media",
"django.template.context_processors.static",
"django.template.context_processors.tz",
"django.template.context_processors.request",
),
},
},
],
)

django.setup()
warnings.simplefilter('always', DeprecationWarning)
warnings.simplefilter("always", DeprecationWarning)
failures = call_command(
'test', test_path, interactive=False, failfast=False, verbosity=2)
"test", test_path, interactive=False, failfast=False, verbosity=2
)

sys.exit(bool(failures))


if __name__ == '__main__':
if __name__ == "__main__":
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), "src"))
parser = OptionParser()

(options, args) = parser.parse_args()
runtests(*args)
runtests(*args)
13 changes: 9 additions & 4 deletions src/automations/flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,12 +404,17 @@ def execute(self, task: models.AutomationTaskModel):
if (
len(all_splits) > 1
): # more than one split at the moment: close this split
if task.message == "Join":
self.store_result(task, "Join", task.result + [task.id])
else:
self.store_result(task, "Join", [task.id])
self.leave(task)
self.store_result(task, "Open Join", []) # Flag as open
return None
else:
all_path_ends = self._automation._db.automationtaskmodel_set.filter(
message="Open Join", status=task.status # Find open
)
self.store_result(
task, "Joined", [tsk.id for tsk in all_path_ends] + [task.id]
)
all_path_ends.update(message="Joined") # Join closed: clear flag
return task

def get_split(self, task):
Expand Down
14 changes: 14 additions & 0 deletions src/automations/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,14 @@ def get_node(self):
instance = self.automation.instance
return getattr(instance, self.status)

def get_previous_tasks(self):
if self.message == "Joined" and self.result:
return self.__class__.objects.filter(id__in=self.result)
return [self.previous] if self.previous else []

def get_next_tasks(self):
return self.automationtaskmodel_set.all()

@classmethod
def get_open_tasks(cls, user):
candidates = cls.objects.filter(finished=None)
Expand Down Expand Up @@ -215,6 +223,12 @@ def get_users_with_permission(
users |= User.objects.filter(is_superuser=True)
return users

def __str__(self):
return f"<ATM {self.status} {self.message} ({self.id})>"

def __repr__(self):
return self.__str__()


def swap_users_with_permission_model_method(model, settings_conf):
"""
Expand Down
40 changes: 19 additions & 21 deletions src/automations/templates/automations/history.html
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
{% extends "automations/base.html" %}{% load i18n %}
{% block content_automations %}{% spaceless %}
<h1>
{{ automation.get_automation_class.get_verbose_name }}<br>
<small>{{ automation.automation_class }}</small>
</h1>
<div class="row">
<div class="col-8">
{% include "automations/includes/history.html" with blocks=blocks %}
</div>
<div class="col-4">
<div class="card">
<h2 class="card-header">{% trans "Automation info" %} {{ automation.id }}</h2>
<ul class="list-group list-group-flush">
<li class="list-group-item">{% trans "Updated:" %} {{ automation.updated }}</li>
<li class="list-group-item">{% trans "Paused until:" %} {{ automation.paused_until }}</li>
<li class="list-group-item">{% trans "Created:" %} {{ automation.created }}</li>
</ul>
<div class="card-body">
<h3 class="h4">{% trans "Data" %}</h3>
<pre>{{ automation.data }}</pre>
</div>
<div class="card mb-4">
<h1 class="card-header">
{{ automation.get_automation_class.get_verbose_name }}
<small>{{ automation.automation_class }}</small>
</h1>
<ul class="list-group list-group-flush">
<li class="list-group-item">{% trans "Automation id" %} {{ automation.id }}</li>
<li class="list-group-item">{% trans "Updated:" %} {{ automation.updated }}</li>
<li class="list-group-item">{% trans "Paused until:" %} {{ automation.paused_until }}</li>
<li class="list-group-item">{% trans "Created:" %} {{ automation.created }}</li>
</ul>
{% if automation.data %}
<div class="card-body">
<h3 class="h4">{% trans "Data" %}</h3>
<pre>{{ automation.data }}</pre>
</div>
</div>
{% endif %}
</div>
{% include "automations/includes/history.html" with tasks=tasks %}
</div>
{% endspaceless %}
{% endblock %}
23 changes: 14 additions & 9 deletions src/automations/templates/automations/includes/history.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
<div class="row">
{% for block in blocks %}
<div class="col">
{% if block.children %}
{% include "automations/includes/history.html" with blocks=block.children %}
{% endif %}
{% include "automations/includes/history_item.html" with task=block.task %}
</div>
{% spaceless %}
{% for task in tasks reversed %}
{% if task|length > 1 %}
<div class="row align-items-center row-cols-{{ task|length }}">
{% for sub_tree in task %}
<div class="col">
{% include "automations/includes/history.html" with tasks=sub_tree %}
</div>
{% endfor %}
</div>
{% else %}
{% include "automations/includes/history_item.html" with task=task %}
{% endif %}
{% endfor %}
</div>
{% endspaceless %}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{% load i18n %}{% spaceless %}
<div class="card mb-3">
<h4 class="card-header{% if "Error" in task.message %} bg-danger{% elif "OK" in task.message %} bg-success{% endif %}">{{ task.status }} = flow.{{ task.get_node.node_name }}()
<h4 class="card-header{% if "Error" in task.message %} bg-danger{% elif "OK" in task.message %} bg-success{% else %} bg-warning{% endif %}">{{ task.status }} = flow.{{ task.get_node.node_name }}()
<small>{% if task.finished %}{{ task.finished }}{% else %}{% trans "running" %}{% endif %}</small></h4>
{% if task.message == "OK" %}<pre class="card-body mb-0">{{ task.result }}</pre>{% endif %}
{% if task.message == "OK" and task.result %}<pre class="card-body mb-0">{{ task.result }}</pre>{% endif %}
{% if "Error" in task.message %}<div class="card-footer"><a href="{% url "traceback" automation.id task.id %}"></a></div>{% endif %}
</div>
{% endspaceless %}
3 changes: 3 additions & 0 deletions src/automations/templates/base.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{# only for testing #}
{% block content %}
{% endblock %}
59 changes: 48 additions & 11 deletions src/automations/tests/test_automations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group
from django.core.management import execute_from_command_line
from django.test import RequestFactory, TestCase, override_settings
from django.test import Client, RequestFactory, TestCase, override_settings
from django.utils.timezone import now
from django.utils.translation import gettext as _

Expand Down Expand Up @@ -88,14 +88,21 @@ class Meta:
)
l10 = Print("Line 10").AfterWaitingUntil(now() - datetime.timedelta(minutes=1))
split = flow.Split().Next("self.t10").Next("self.t20").Next("self.t30")

join = flow.Join()
l20 = Print("All joined now")
l30 = flow.End()

t10 = Print("Thread 10").Next(this.join)
t10 = Print("Thread 10").Next(this.split_again)
t20 = Print("Thread 20").Next(this.join)
t30 = Print("Thread 30").Next(this.join)

split_again = flow.Split().Next(this.t40).Next(this.t50)
t40 = Print("Thread 40").Next(this.join_again)
t50 = Print("Thread 50").Next(this.join_again)
join_again = flow.Join()
going_back = Print("Sub split joined").Next(this.join)


class AtmTaskForm(forms.ModelForm):
class Meta:
Expand Down Expand Up @@ -132,7 +139,7 @@ class Looping(flow.Automation):


class BoundToFail(flow.Automation):
start = Print("Will divide by zero.")
start = Print("Will divide by zero.").SkipAfter(datetime.timedelta(days=1))
div = flow.Execute(lambda x: 5 / 0).OnError(this.error_node)
never = Print("This should NOT be printed")
not_caught = flow.Execute(lambda x: 5 / 0)
Expand Down Expand Up @@ -163,7 +170,9 @@ def test_modelsetup(self):
x = TestAutomation(autorun=False)
qs = AutomationModel.objects.all()
self.assertEqual(len(qs), 1)
self.assertEqual(qs[0].automation_class, "automations.tests.test_automations.TestAutomation")
self.assertEqual(
qs[0].automation_class, "automations.tests.test_automations.TestAutomation"
)

self.assertEqual(get_automation_class(x._db.automation_class), TestAutomation)

Expand Down Expand Up @@ -193,6 +202,9 @@ def test_split_join(self):
self.assertIn("t10 Thread 10", output)
self.assertIn("t20 Thread 20", output)
self.assertIn("t30 Thread 30", output)
self.assertIn("t40 Thread 40", output)
self.assertIn("t50 Thread 50", output)
self.assertIn("going_back Sub split joined", output)

self.assertEqual(atm.get_verbose_name(), "Allow to split and join")
self.assertEqual(atm.get_verbose_name_plural(), "Allow splitS and joinS")
Expand Down Expand Up @@ -258,6 +270,29 @@ def test_form(self):
self.assertEqual(len(atm.form2.get_users_with_permission()), 0)


class HistoryTestCase(TestCase):
def setUp(self):
# Every test needs access to the request factory.
self.client = Client()
self.admin = User.objects.create_user(
username="admin",
email="admin@...",
password="Even More Secr3t",
is_staff=True,
is_superuser=True,
)
self.admin.save()
self.assertEqual(self.admin.is_superuser, True)
login = self.client.login(username="admin", password="Even More Secr3t")
self.failUnless(login, "Could not login")

def test_history_test(self):
atm = TestSplitJoin()
response = self.client.get(f"/dashboard/{atm._db.id}")
self.assertEqual(response.status_code, 200)
self.assertIn("split_again = flow.Split()", response.content.decode("utf8"))


test_signal = django.dispatch.Signal()


Expand Down Expand Up @@ -367,9 +402,9 @@ def test_managment_delete_command(self):
execute_from_command_line(["manage.py", "automation_delete_history", "0"])
output = fake_out.getvalue().splitlines()
self.assertIn(
"12 total objects deleted, including 1 AutomationModel instances, and 11 "
"18 total objects deleted, including 1 AutomationModel instances, and 17 "
"AutomationTaskModel instances",
output
output,
)
self.assertEqual(AutomationModel.objects.count(), 0)
self.assertEqual(AutomationTaskModel.objects.count(), 0)
Expand Down Expand Up @@ -513,7 +548,6 @@ def test_errors(self):
self.assertEqual(
atm._db.automationtaskmodel_set.all()[1].message,
"SyntaxError('Darn, this is not good')",

)
self.assertIn(
"error",
Expand Down Expand Up @@ -631,9 +665,7 @@ def test_method_swaps(self):


class AutomationReprTest(TestCase):

def test_automation_repr(self):

class TinyAutomation(flow.Automation):
start = flow.Execute(this.init)
intermediate = flow.Execute(this.end)
Expand All @@ -644,8 +676,13 @@ def init(self, task, *args, **kwargs):

automation_dict = str(TinyAutomation.__dict__)

self.assertIn("'__module__': 'automations.tests.test_automations'", automation_dict)
self.assertIn(
"'__module__': 'automations.tests.test_automations'", automation_dict
)
self.assertIn("'start': <unbound Execute node>", automation_dict)
self.assertIn("'intermediate': <unbound Execute node>", automation_dict)
self.assertIn("'end': <unbound End node>", automation_dict)
self.assertIn("'init': <function AutomationReprTest.test_automation_repr.", automation_dict)
self.assertIn(
"'init': <function AutomationReprTest.test_automation_repr.",
automation_dict,
)
Loading

0 comments on commit e1ec9e8

Please sign in to comment.