Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle empty testsuites in the XML importer #80

Merged
merged 1 commit into from
Oct 27, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions backend/ibutsu_server/controllers/import_controller.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import connexion
from ibutsu_server.db.base import session
from ibutsu_server.db.models import Import
from ibutsu_server.db.models import ImportFile
from ibutsu_server.tasks.importers import run_archive_import
from ibutsu_server.tasks.importers import run_junit_import
from ibutsu_server.util.projects import get_project_id


def get_import(id_):
Expand All @@ -17,7 +19,7 @@ def get_import(id_):
return import_.to_dict()


def add_import(import_file=None, *args, **kwargs):
def add_import(import_file=None, project=None, *args, **kwargs):
"""Imports a JUnit XML file and creates a test run and results from it.

:param import_file: file to upload
Expand All @@ -27,8 +29,13 @@ def add_import(import_file=None, *args, **kwargs):
"""
if not import_file:
return "Bad request, no file uploaded", 400
data = {}
if connexion.request.form.get("project"):
project = connexion.request.form["project"]
if project:
data["project_id"] = get_project_id(project)
new_import = Import.from_dict(
**{"status": "pending", "filename": import_file.filename, "format": "", "data": {}}
**{"status": "pending", "filename": import_file.filename, "format": "", "data": data}
)
session.add(new_import)
session.commit()
Expand Down
3 changes: 3 additions & 0 deletions backend/ibutsu_server/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,9 @@ paths:
description: The file to import
format: binary
type: string
project:
description: The project associated with this import
type: string
required:
- importFile
type: object
Expand Down
16 changes: 12 additions & 4 deletions backend/ibutsu_server/tasks/importers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from lxml import objectify


def _create_result(tar, run_id, result, artifacts):
def _create_result(tar, run_id, result, artifacts, project_id=None):
"""Create a result with artifacts, used in the archive importer"""
old_id = None
result_id = convert_objectid_to_uuid(result.get("id"))
Expand All @@ -33,6 +33,8 @@ def _create_result(tar, run_id, result, artifacts):
if "id" in result:
result.pop("id")
result["run_id"] = run_id
if project_id:
result["project_id"] = project_id
result_record = Result.from_dict(**result)
session.add(result_record)
session.commit()
Expand Down Expand Up @@ -85,14 +87,16 @@ def run_junit_import(import_):
"tests": testsuite.get("tests"),
},
}
if import_record.data.get("project_id"):
run_dict["project_id"] = import_record.data["project_id"]
# Insert the run, and then update the import with the run id
run = Run.from_dict(**run_dict)
session.add(run)
session.commit()
run_dict = run.to_dict()
import_record.data["run_id"].append(run.id)
# Import the contents of the XML file
for testcase in testsuite.testcase:
for testcase in testsuite.iterchildren(tag="testcase"):
test_name = testcase.get("name").split(".")[-1]
if testcase.get("classname"):
test_name = testcase.get("classname").split(".")[-1] + "." + test_name
Expand All @@ -109,6 +113,8 @@ def run_junit_import(import_):
"params": {},
"source": testsuite.get("name"),
}
if import_record.data.get("project_id"):
result_dict["project_id"] = import_record.data["project_id"]
skip_reason, traceback = None, None
if testcase.find("failure"):
result_dict["result"] = "failed"
Expand Down Expand Up @@ -231,7 +237,9 @@ def run_archive_import(import_):
elif not run_dict.get("created") and not run_dict.get("start_time"):
run_dict["created"] = start_time
run_dict["start_time"] = start_time
if run_dict.get("metadata", {}).get("project"):
if import_record.data.get("project_id"):
run_dict["project_id"] = import_record.data["project_id"]
elif run_dict.get("metadata", {}).get("project"):
run_dict["project_id"] = get_project_id(run_dict["metadata"]["project"])
# If this run has a valid ObjectId, check if this run exists
if is_uuid(run_dict.get("id")):
Expand All @@ -246,7 +254,7 @@ def run_archive_import(import_):
# Now loop through all the results, and create or update them
for result in results:
artifacts = result_artifacts.get(result["id"], [])
_create_result(tar, run.id, result, artifacts)
_create_result(tar, run.id, result, artifacts, import_record.data.get("project_id"))
# Update the import record
_update_import_status(import_record, "done")
if run:
Expand Down
28 changes: 26 additions & 2 deletions backend/ibutsu_server/test/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import logging
from copy import copy
from unittest.mock import MagicMock
from inspect import isfunction

import ibutsu_server.tasks
from flask_testing import TestCase
Expand All @@ -9,6 +9,27 @@
from ibutsu_server.util import merge_dicts


def mock_task(*args, **kwargs):
if args and isfunction(args[0]):
func = args[0]

def wrap(*args, **kwargs):
return func(*args, **kwargs)

wrap._orig_func = func
return wrap
else:

def decorate(func):
def _wrapped(*args, **kwargs):
return func(*args, **kwargs)

_wrapped._orig_func = func
return _wrapped

return decorate


class BaseTestCase(TestCase):
def create_app(self):
logging.getLogger("connexion.operation").setLevel("ERROR")
Expand All @@ -19,6 +40,9 @@ def create_app(self):
}
app = get_app(**extra_config)
create_celery_app(app.app)

if ibutsu_server.tasks.task is None:
ibutsu_server.tasks.task = mock_task
return app.app

def assert_201(self, response, message=None):
Expand Down Expand Up @@ -142,4 +166,4 @@ class MockRun(MockModel):


# Mock out the task decorator
ibutsu_server.tasks.task = MagicMock()
ibutsu_server.tasks.task = mock_task
9 changes: 9 additions & 0 deletions backend/ibutsu_server/test/res/empty-testsuite.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<testsuites name="Tests" time="15.943" tests="1" failures="0">
<testsuite name="Root Suite" timestamp="2020-10-05T20:41:16" tests="0" file="root-suite.spec.js" failures="0" time="0">
</testsuite>
<testsuite name="Visual Testing" timestamp="2020-10-05T20:41:16" tests="1" failures="0" time="15.943">
<testcase name="Test navigation" time="15.943" classname="navigation">
</testcase>
</testsuite>
</testsuites>
60 changes: 37 additions & 23 deletions backend/ibutsu_server/test/test_tasks.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,63 @@
import time
import uuid
from datetime import datetime
from unittest.mock import MagicMock
from unittest.mock import patch

import pytest
from ibutsu_server.test import BaseTestCase

MOCK_RUN_ID = str(uuid.uuid4())
MOCK_RUN = {"id": MOCK_RUN_ID}
MOCK_RUN = MagicMock(**{"id": MOCK_RUN_ID, "data": {}})
MOCK_PROJECT = str(uuid.uuid4())
MOCK_TIME = time.time()
MOCK_RESULTS = [
{
"result": "passed",
MagicMock(
**{
"result": "passed",
"duration": 1.532734345,
"data": {"component": "login", "env": "qa", "project": MOCK_PROJECT},
"starttime": MOCK_TIME,
}
)
]
UPDATED_RUN = MagicMock(
**{
"id": MOCK_RUN_ID,
"duration": 1.532734345,
"metadata": {"component": "login", "env": "qa", "project": MOCK_PROJECT},
"starttime": MOCK_TIME,
"summary": {
"errors": 0,
"failures": 0,
"skips": 0,
"xfailures": 0,
"xpasses": 0,
"tests": 1,
},
"data": {"component": "login", "env": "qa", "project": MOCK_PROJECT},
"start_time": MOCK_TIME,
"created": datetime.fromtimestamp(MOCK_TIME).isoformat(),
}
]
UPDATED_RUN = {
"id": MOCK_RUN_ID,
"duration": 1.532734345,
"summary": {"errors": 0, "failures": 0, "skips": 0, "xfailures": 0, "xpasses": 0, "tests": 1},
"metadata": {"component": "login", "env": "qa", "project": MOCK_PROJECT},
"start_time": MOCK_TIME,
"created": datetime.fromtimestamp(MOCK_TIME).isoformat(),
}
)


@pytest.mark.skip("Currently breaking collection")
class TestRunTasks(BaseTestCase):
def test_update_run(self, mocker):
@patch("ibutsu_server.tasks.runs.Result")
@patch("ibutsu_server.tasks.runs.Run")
@patch("ibutsu_server.tasks.runs.session")
@patch("ibutsu_server.tasks.runs.lock")
def test_update_run(self, mocked_lock, mocked_session, mocked_run, mocked_result):
"""Test updating the run"""
from ibutsu_server.tasks.runs import update_run

mocker.patch("ibutsu_server.tasks.runs.lock")
mocked_session = mocker.patch("ibutsu_server.tasks.runs.session")
mocked_run = mocker.patch("ibutsu_server.tasks.runs.Run")
mocked_lock.return_value.__enter__.return_value = None
mocked_run.query.get.return_value = MOCK_RUN
mocked_result = mocker.patch("ibutsu_server.tasks.runs.Result")
mocked_result.query.return_value.filter.return_value.all.return_value = MOCK_RESULTS

update_run = update_run._orig_func
update_run(MOCK_RUN_ID)

mocked_lock.assert_called_once()
mocked_run.query.get.assert_called_once_with(MOCK_RUN_ID)
mocked_result.query.return_value.filter.assert_called_once()
mocked_result.query.return_value.filter.return_value.all.assert_called_once()
mocked_result.query.filter.assert_called_once()
mocked_result.query.filter.return_value.order_by.return_value.all.assert_called_once()
mocked_session.add.assert_called_once()
mocked_session.commit.assert_called_once()