Skip to content

Commit

Permalink
Merge pull request #1649 from csordasmarton/remove-run-results
Browse files Browse the repository at this point in the history
Delete filtered run results
  • Loading branch information
gyorb authored Aug 9, 2018
2 parents d716b9c + 6a71383 commit a4551d9
Show file tree
Hide file tree
Showing 8 changed files with 293 additions and 48 deletions.
14 changes: 14 additions & 0 deletions api/v6/report_server.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -382,10 +382,24 @@ service codeCheckerDBAccess {
string getPackageVersion();

// remove bug results from the database
// !!! DEPRECATED !!!
// Use removeRun to remove the whole run or removeRunReports to remove
// filtered run results.
// PERMISSION: PRODUCT_STORE
bool removeRunResults(1: list<i64> runIds)

// remove bug results from the database
// PERMISSION: PRODUCT_STORE
bool removeRunReports(1: list<i64> runIds,
2: ReportFilter reportFilter,
3: CompareData cmpData)
throws (1: shared.RequestFailed requestError),

// Remove run from the database.
// PERMISSION: PRODUCT_STORE
bool removeRun(1: i64 runId)
throws (1: shared.RequestFailed requestError),

// get the suppress file path set by the command line
// returns empty string if not set
// PERMISSION: PRODUCT_ACCESS
Expand Down
6 changes: 3 additions & 3 deletions libcodechecker/cmd/cmd_line_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -1116,9 +1116,9 @@ def condition(name, runid, date):
def condition(name, runid, date):
return False

client.removeRunResults([run.runId for run
in client.getRunData(None)
if condition(run.name, run.runId, run.runDate)])
for run_id in [run.runId for run in client.getRunData(None)
if condition(run.name, run.runId, run.runDate)]:
client.removeRun(run_id)

LOG.info("Done.")

Expand Down
8 changes: 8 additions & 0 deletions libcodechecker/libclient/thrift_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ def getRunResultTypes(self, runId, reportFilters):
def removeRunResults(self, run_ids):
pass

@ThriftClientCall
def removeRunReports(self, run_ids, report_filter, cmp_data):
pass

@ThriftClientCall
def removeRun(self, run_id):
pass

@ThriftClientCall
def getSuppressedBugs(self, run_id):
pass
Expand Down
135 changes: 100 additions & 35 deletions libcodechecker/server/api/report_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,34 @@ def get_report_hashes(session, run_ids, tag_ids):
return set([t[0] for t in q])


def check_remove_runs_lock(session, run_ids):
"""
Check if there is an existing lock on the given runs, which has not
expired yet. If so, the run cannot be deleted, as someone is assumed to
be storing into it.
"""
locks_expired_at = datetime.now() - timedelta(
seconds=db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE)

run_locks = session.query(RunLock.name) \
.filter(RunLock.locked_at >= locks_expired_at)

if run_ids:
run_locks = run_locks.filter(Run.id.in_(run_ids))

run_locks = run_locks \
.outerjoin(Run,
Run.name == RunLock.name) \
.all()

if run_locks:
raise shared.ttypes.RequestFailed(
shared.ttypes.ErrorCode.DATABASE,
"Can not remove results because the following runs "
"are locked: {0}".format(
', '.join([r[0] for r in run_locks])))


class ThriftRequestHandler(object):
"""
Connect to database and handle thrift client requests.
Expand Down Expand Up @@ -1758,51 +1786,88 @@ def removeRunResults(self, run_ids):
failed = False
for run_id in run_ids:
try:
with DBSession(self.__Session) as session:
LOG.debug('Run id to delete: ' + str(run_id))
self.removeRun(run_id)
except Exception as ex:
LOG.error("Failed to remove run: " + str(run_id))
LOG.error(ex)
failed = True
return not failed

run_to_delete = session.query(Run).get(run_id)
if not run_to_delete.can_delete:
LOG.debug("Can't delete " + str(run_id))
continue
@exc_to_thrift_reqfail
@timeit
def removeRunReports(self, run_ids, report_filter, cmp_data):
self.__require_store()

# Check if there is an existing lock on the given run name,
# which has not expired yet. If so, the run cannot be
# deleted, as someone is assumed to be storing into it.
locks_expired_at = datetime.now() - \
timedelta(
seconds=db_cleanup.RUN_LOCK_TIMEOUT_IN_DATABASE)
lock = session.query(RunLock) \
.filter(RunLock.name == run_to_delete.name,
RunLock.locked_at >= locks_expired_at) \
.one_or_none()
if lock:
LOG.info("Can't delete '{0}' as it is locked."
.format(run_to_delete.name))
continue
if not run_ids:
run_ids = []

run_to_delete.can_delete = False
# Commmit the can_delete flag.
session.commit()
if cmp_data and cmp_data.runIds:
run_ids.extend(cmp_data.runIds)

session.query(Run)\
.filter(Run.id == run_id)\
.delete(synchronize_session=False)
with DBSession(self.__Session) as session:
check_remove_runs_lock(session, run_ids)

# Delete files and contents that are not present
# in any bug paths.
db_cleanup.remove_unused_files(session)
session.commit()
session.close()
try:
diff_hashes = None
if cmp_data:
diff_hashes, _ = self._cmp_helper(session,
run_ids,
report_filter,
cmp_data)
if not diff_hashes:
# There is no difference.
return True

filter_expression = process_report_filter(session,
report_filter)

q = session.query(Report.id) \
.outerjoin(File, Report.file_id == File.id) \
.outerjoin(ReviewStatus,
ReviewStatus.bug_hash == Report.bug_id) \
.filter(filter_expression)

if run_ids:
q = q.filter(Report.run_id.in_(run_ids))

if cmp_data:
q = q.filter(Report.bug_id.in_(diff_hashes))

reports_to_delete = [r[0] for r in q]
if len(reports_to_delete) != 0:
session.query(Report) \
.filter(Report.id.in_(reports_to_delete)) \
.delete(synchronize_session=False)

# Delete files and contents that are not present
# in any bug paths.
db_cleanup.remove_unused_files(session)
session.commit()
session.close()
return True
except Exception as ex:
LOG.error("Failed to remove run: " + str(run_id))
session.rollback()
LOG.error("Database cleanup failed.")
LOG.error(ex)
failed = True
return False

return not failed
@exc_to_thrift_reqfail
@timeit
def removeRun(self, run_id):
self.__require_store()

# Remove the whole run.
with DBSession(self.__Session) as session:
check_remove_runs_lock(session, [run_id])

session.query(Run) \
.filter(Run.id == run_id) \
.delete(synchronize_session=False)
session.commit()
session.close()

return True

# -----------------------------------------------------------------------
@exc_to_thrift_reqfail
def getSuppressFile(self):
"""
Expand Down
102 changes: 102 additions & 0 deletions tests/functional/report_viewer_api/test_remove_run_results.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#
# -----------------------------------------------------------------------------
# The CodeChecker Infrastructure
# This file is distributed under the University of Illinois Open Source
# License. See LICENSE.TXT for details.
# -----------------------------------------------------------------------------

"""
Test removing run results feature.
"""
from __future__ import print_function
from __future__ import division
from __future__ import absolute_import

import os
import sys
import unittest

from codeCheckerDBAccess_v6.ttypes import ReportFilter, RunFilter

from libtest import codechecker
from libtest import env
from libtest import project


class RemoveRunResults(unittest.TestCase):
""" Tests for removing run results. """

_ccClient = None

def setUp(self):
test_workspace = os.environ['TEST_WORKSPACE']

test_class = self.__class__.__name__
print('Running ' + test_class + ' tests in ' + test_workspace)

self._codechecker_cfg = env.import_codechecker_cfg(test_workspace)
self._test_dir = os.path.join(test_workspace, 'test_files')

# Get the clang version which is tested.
self._clang_to_test = env.clang_to_test()

self._testproject_data = env.setup_test_proj_cfg(test_workspace)
self.assertIsNotNone(self._testproject_data)

self._cc_client = env.setup_viewer_client(test_workspace)
self.assertIsNotNone(self._cc_client)

# Get the run names which belong to this test.
run_names = env.get_run_names(test_workspace)

runs = self._cc_client.getRunData(None)

test_runs = [run for run in runs if run.name in run_names]

self._runid = test_runs[0].runId

def test_remove_run_results(self):
"""
Test for removing run results and run.
"""
# Run the anaysis again with different setup.
test_project_path = self._testproject_data['project_path']
ret = project.clean(test_project_path)
if ret:
sys.exit(ret)

codechecker.check(self._codechecker_cfg,
'remove_run_results',
test_project_path)

run_filter = RunFilter(names=['remove_run_results'], exactMatch=True)
runs = self._cc_client.getRunData(run_filter)
self.assertEqual(len(runs), 1)

run_id = runs[0].runId

orig_results_count = \
self._cc_client.getRunResultCount([run_id], None, None)
self.assertNotEqual(orig_results_count, 0)

checker_filter = ReportFilter(checkerName=["core.CallAndMessage"])
res_count = self._cc_client.getRunResultCount([run_id],
checker_filter,
None)
self.assertNotEqual(res_count, 0)

self._cc_client.removeRunReports([run_id],
checker_filter,
None)

res_count = self._cc_client.getRunResultCount([run_id],
checker_filter,
None)
self.assertEqual(res_count, 0)

# Remove the run.
self._cc_client.removeRun(run_id)

# Check that we removed all results from the run.
res = self._cc_client.getRunResultCount([run_id], None, None)
self.assertEqual(res, 0)
10 changes: 9 additions & 1 deletion www/scripts/codecheckerviewer/ListOfRuns.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,15 @@ function (declare, dom, ItemFileWriteStore, topic, Dialog, Button,
}
});

CC_SERVICE.removeRunResults(that.deleteRunIds, function (success) {});
that.deleteRunIds.forEach(function (runId) {
CC_SERVICE.removeRun(runId, function () {}).fail(
function (jsReq, status, exc) {
new Dialog({
title : 'Failure!',
content : exc.message
}).show();
});
});

that.deleteRunIds = [];
that.update();
Expand Down
Loading

0 comments on commit a4551d9

Please sign in to comment.