kopia lustrzana https://github.com/OpenDroneMap/WebODM
Unit tests, cleanup
rodzic
1717d09b9e
commit
6fcf3fbdc8
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()
|
|
@ -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)}
|
Ładowanie…
Reference in New Issue