Unit tests, cleanup

pull/782/head
Piero Toffanin 2020-01-20 16:52:01 -05:00
rodzic 1717d09b9e
commit 6fcf3fbdc8
8 zmienionych plików z 109 dodań i 207 usunięć

Wyświetl plik

@ -1,7 +1,7 @@
import os
import mimetypes
from worker.celery import app as celery
from worker.tasks import TestSafeAsyncResult
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status, permissions
@ -14,7 +14,8 @@ class CheckTask(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, celery_task_id=None, **kwargs):
res = celery.AsyncResult(celery_task_id)
res = TestSafeAsyncResult(celery_task_id)
if not res.ready():
return Response({'ready': False}, status=status.HTTP_200_OK)
else:
@ -43,7 +44,7 @@ class GetTaskResult(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request, celery_task_id=None, **kwargs):
res = celery.AsyncResult(celery_task_id)
res = TestSafeAsyncResult(celery_task_id)
if res.ready():
result = res.get()
file = result.get('file', None) # File path

Wyświetl plik

@ -45,7 +45,6 @@ export default class LayersControlLayer extends React.Component {
if (mUrl){
for (let d of mUrlToDownload){
const idx = mUrl.lastIndexOf(d.url);
console.log(mUrl);
if (idx !== -1){
this.downloadFileUrl = mUrl.substr(0, idx) + "download/" + d.download;
break;

Wyświetl plik

@ -4,190 +4,6 @@
{% block navbar-top-links %}
<ul class="nav navbar-top-links navbar-right">
<!--<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-envelope fa-fw"></i> <i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu dropdown-messages">
<li>
<a href="#">
<div>
<strong>John Smith</strong>
<span class="pull-right text-muted">
<em>Yesterday</em>
</span>
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend...</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<strong>John Smith</strong>
<span class="pull-right text-muted">
<em>Yesterday</em>
</span>
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend...</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<strong>John Smith</strong>
<span class="pull-right text-muted">
<em>Yesterday</em>
</span>
</div>
<div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque eleifend...</div>
</a>
</li>
<li class="divider"></li>
<li>
<a class="text-center" href="#">
<strong>Read All Messages</strong>
<i class="fa fa-angle-right"></i>
</a>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-tasks fa-fw"></i> <i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu dropdown-tasks">
<li>
<a href="#">
<div>
<p>
<strong>Task 1</strong>
<span class="pull-right text-muted">40% Complete</span>
</p>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="40" aria-valuemin="0" aria-valuemax="100" style="width: 40%">
<span class="sr-only">40% Complete (success)</span>
</div>
</div>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<p>
<strong>Task 2</strong>
<span class="pull-right text-muted">20% Complete</span>
</p>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="20" aria-valuemin="0" aria-valuemax="100" style="width: 20%">
<span class="sr-only">20% Complete</span>
</div>
</div>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<p>
<strong>Task 3</strong>
<span class="pull-right text-muted">60% Complete</span>
</p>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="60" aria-valuemin="0" aria-valuemax="100" style="width: 60%">
<span class="sr-only">60% Complete (warning)</span>
</div>
</div>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<p>
<strong>Task 4</strong>
<span class="pull-right text-muted">80% Complete</span>
</p>
<div class="progress progress-striped active">
<div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="80" aria-valuemin="0" aria-valuemax="100" style="width: 80%">
<span class="sr-only">80% Complete (danger)</span>
</div>
</div>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a class="text-center" href="#">
<strong>See All Tasks</strong>
<i class="fa fa-angle-right"></i>
</a>
</li>
</ul>
</li>
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-bell fa-fw"></i> <i class="fa fa-caret-down"></i>
</a>
<ul class="dropdown-menu dropdown-alerts">
<li>
<a href="#">
<div>
<i class="fa fa-comment fa-fw"></i> New Comment
<span class="pull-right text-muted small">4 minutes ago</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<i class="fa fa-twitter fa-fw"></i> 3 New Followers
<span class="pull-right text-muted small">12 minutes ago</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<i class="fa fa-envelope fa-fw"></i> Message Sent
<span class="pull-right text-muted small">4 minutes ago</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<i class="fa fa-tasks fa-fw"></i> New Task
<span class="pull-right text-muted small">4 minutes ago</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a href="#">
<div>
<i class="fa fa-upload fa-fw"></i> Server Rebooted
<span class="pull-right text-muted small">4 minutes ago</span>
</div>
</a>
</li>
<li class="divider"></li>
<li>
<a class="text-center" href="#">
<strong>See All Alerts</strong>
<i class="fa fa-angle-right"></i>
</a>
</li>
</ul>
</li> -->
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#">
<i class="fa fa-user fa-fw"></i> <i class="fa fa-caret-down"></i>
@ -210,22 +26,9 @@
<div class="navbar-default sidebar" role="navigation">
<div class="sidebar-nav navbar-collapse">
<ul class="nav" id="side-menu">
<!--<li class="sidebar-search">
<div class="input-group custom-search-form">
<input type="text" class="form-control" placeholder="Search...">
<span class="input-group-btn">
<button class="btn btn-default" type="button">
<i class="fa fa-search"></i>
</button>
</span>
</div>
</li>-->
<li>
<a href="/dashboard/"><i class="fa fa-tachometer-alt fa-fw"></i> {% trans 'Dashboard' %}</a>
</li>
<!--<li>
<a href="#"><i class="fa fa-plane fa-fw"></i> Mission Planner</a>
</li> -->
{% load processingnode_extras plugins %}
{% can_view_processing_nodes as view_nodes %}
{% can_add_processing_nodes as add_nodes %}

Wyświetl plik

@ -2,7 +2,9 @@ import io
import os
import time
import threading
from worker.celery import app as celery
import logging
from datetime import timedelta
@ -41,6 +43,8 @@ DELAY = 2 # time to sleep for during process launch, background processing, etc
class TestApiTask(BootTransactionTestCase):
def setUp(self):
super().setUp()
def tearDown(self):
clear_test_media_root()
def test_task(self):
@ -282,6 +286,13 @@ class TestApiTask(BootTransactionTestCase):
})
self.assertTrue(res.status_code == status.HTTP_404_NOT_FOUND)
# Cannot export orthophoto
res = client.post("/api/projects/{}/tasks/{}/orthophoto/export".format(project.id, task.id), {
'formula': 'NDVI',
'bands': 'RGN'
})
self.assertEqual(res.status_code, status.HTTP_404_NOT_FOUND)
# No UUID at this point
self.assertTrue(len(task.uuid) == 0)
@ -316,6 +327,8 @@ class TestApiTask(BootTransactionTestCase):
# Progress is 100%
self.assertTrue(task.running_progress == 1.0)
time.sleep(0.5)
handler.assert_any_call(
sender=Task,
task_id=task.id,
@ -363,6 +376,40 @@ class TestApiTask(BootTransactionTestCase):
res = client.get("/api/projects/{}/tasks/{}/assets/odm_orthophoto/odm_orthophoto.tif".format(project.id, task.id))
self.assertTrue(res.status_code == status.HTTP_200_OK)
# Can export orthophoto (when formula and bands are specified)
res = client.post("/api/projects/{}/tasks/{}/orthophoto/export".format(project.id, task.id), {
'formula': 'NDVI'
})
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
res = client.post("/api/projects/{}/tasks/{}/orthophoto/export".format(project.id, task.id), {
'bands': 'RGN'
})
self.assertEqual(res.status_code, status.HTTP_400_BAD_REQUEST)
res = client.post("/api/projects/{}/tasks/{}/orthophoto/export".format(project.id, task.id), {
'formula': 'NDVI',
'bands': 'RGN'
})
self.assertEqual(res.status_code, status.HTTP_200_OK)
reply = json.loads(res.content.decode("utf-8"))
self.assertTrue("celery_task_id" in reply)
celery_task_id = reply["celery_task_id"]
# Check export status
res = client.get("/api/workers/check/{}".format(celery_task_id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
reply = json.loads(res.content.decode("utf-8"))
self.assertTrue("ready" in reply)
self.assertEqual(reply["ready"], True)
# Can download exported orthophoto
res = client.get("/api/workers/get/{}?filename=odm_orthophoto_NDVI.tif".format(celery_task_id))
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEquals(res.get('Content-Disposition'), "attachment; filename=odm_orthophoto_NDVI.tif")
with Image.open(io.BytesIO(res.content)) as i:
self.assertEqual(i.width, 212)
self.assertEqual(i.height, 212)
# Can access tiles.json, bounds and metadata
for ep in endpoints:
for tile_type in tile_types:

Wyświetl plik

@ -1,8 +1,9 @@
import os
from stat import ST_ATIME, ST_MTIME
import json
import worker
from app import pending_actions
from app.models import Project
from app.models import Task
from nodeodm.models import ProcessingNode
@ -10,6 +11,8 @@ from webodm import settings
from .classes import BootTestCase
from .utils import start_processing_node
from worker.tasks import redis_client
from rest_framework.test import APIClient
from rest_framework import status
class TestWorker(BootTestCase):
def setUp(self):
@ -80,3 +83,19 @@ class TestWorker(BootTestCase):
# After 24 hours it should get removed
worker.tasks.cleanup_tmp_directory()
self.assertFalse(os.path.exists(tmpdir))
def test_workers_api(self):
client = APIClient()
# Can check bogus worker task status
res = client.get("/api/workers/check/bogus")
self.assertEqual(res.status_code, status.HTTP_200_OK)
reply = json.loads(res.content.decode("utf-8"))
self.assertEqual(reply["ready"], False)
# Can get bogus worker task status
res = client.get("/api/workers/get/bogus")
self.assertEqual(res.status_code, status.HTTP_200_OK)
reply = json.loads(res.content.decode("utf-8"))
self.assertEqual(reply["error"], "Task not ready")

Wyświetl plik

@ -1,7 +1,7 @@
{
"name": "WebODM",
"version": "1.2.2",
"description": "Open Source Drone Image Processing",
"version": "1.3.0",
"description": "User-friendly, extendable application and API for processing aerial imagery.",
"main": "index.js",
"scripts": {
"test": "python manage.py test app.tests.test_generate_ui_mocks && jest",

Wyświetl plik

@ -41,5 +41,27 @@ app.conf.beat_schedule = {
},
}
# Mock class for handling async results during testing
class MockAsyncResult:
def __init__(self, celery_task_id, result = None):
self.celery_task_id = celery_task_id
if result is None:
if celery_task_id == 'bogus':
self.result = None
else:
self.result = MockAsyncResult.results.get(celery_task_id)
else:
self.result = result
MockAsyncResult.results[celery_task_id] = result
def get(self):
return self.result
def ready(self):
return self.result is not None
MockAsyncResult.results = {}
MockAsyncResult.set = lambda cti, r: MockAsyncResult(cti, r)
if __name__ == '__main__':
app.start()

Wyświetl plik

@ -16,6 +16,7 @@ from app.plugins.grass_engine import grass, GrassEngineException
from nodeodm import status_codes
from nodeodm.models import ProcessingNode
from webodm import settings
import worker
from .celery import app
from app.raster_utils import export_raster_index as export_raster_index_sync
import redis
@ -23,6 +24,9 @@ import redis
logger = get_task_logger("app.logger")
redis_client = redis.Redis.from_url(settings.CELERY_BROKER_URL)
# What class to use for async results, since during testing we need to mock it
TestSafeAsyncResult = worker.celery.MockAsyncResult if settings.TESTING else app.AsyncResult
@app.task
def update_nodes_info():
processing_nodes = ProcessingNode.objects.all()
@ -145,15 +149,22 @@ def execute_grass_script(script, serialized_context = {}, out_key='output'):
ctx = grass.create_context(serialized_context)
return {out_key: ctx.execute(script), 'context': ctx.serialize()}
except GrassEngineException as e:
logger.error(str(e))
return {'error': str(e), 'context': ctx.serialize()}
@app.task
def export_raster_index(input, expression):
@app.task(bind=True)
def export_raster_index(self, input, expression):
try:
logger.info("Exporting raster index {} with expression: {}".format(input, expression))
tmpfile = tempfile.mktemp('_raster_index.tif', dir=settings.MEDIA_TMP)
export_raster_index_sync(input, expression, tmpfile)
return {'file': tmpfile}
result = {'file': tmpfile}
if settings.TESTING:
TestSafeAsyncResult.set(self.request.id, result)
return result
except Exception as e:
logger.error(str(e))
return {'error': str(e)}