From 0d641aa214b16c276dd0910d105eb1f55ef55c02 Mon Sep 17 00:00:00 2001 From: Adailson Santos Date: Oct 01 2017 20:40:49 +0000 Subject: [PATCH 1/6] [REPORTS] Added model to reports - Create models for reports counter - Added save method for Reports on monitor Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/model.py b/kiskadee/model.py index c06d3ee..ee860db 100644 --- a/kiskadee/model.py +++ b/kiskadee/model.py @@ -3,11 +3,15 @@ from sqlalchemy.ext.declarative import declarative_base from sqlalchemy import Column, Integer, UnicodeText, UniqueConstraint,\ Sequence, Unicode, ForeignKey, orm, JSON - import kiskadee Base = declarative_base() +"""class TypeEnum(enum.Enum): + Enum for to use on TypeReport class + error = 1 + style = 2 + warning = 3""" class Package(Base): """Software packages abstraction. @@ -27,7 +31,6 @@ class Package(Base): UniqueConstraint('name', 'fetcher_id'), ) - class Fetcher(Base): """kiskadee fetcher abstraction.""" @@ -74,7 +77,16 @@ class Analysis(Base): version_id = Column(Integer, ForeignKey('versions.id'), nullable=False) analyzer_id = Column(Integer, ForeignKey('analyzers.id'), nullable=False) raw = Column(JSON) + report = orm.relationship('Reports', backref='analysis') +class Reports(Base): + """Abstraction of a analysis report""" + __tablename__ = 'reports' + id = Column(Integer, + Sequence('reports_id_seq', optional=True), primary_key=True) + report_type = Column(Unicode(100), nullable=False) + counter = Column(Integer) + analysis_id = Column(Integer, ForeignKey('analysis.id'), nullable=False) def create_analyzers(_session): """Create the analyzers on database. diff --git a/kiskadee/monitor.py b/kiskadee/monitor.py index 01f10ca..6b84724 100644 --- a/kiskadee/monitor.py +++ b/kiskadee/monitor.py @@ -12,7 +12,8 @@ import json import kiskadee.database from kiskadee.runner import Runner import kiskadee.queue -from kiskadee.model import Package, Fetcher, Version +from kiskadee.model import Package, Fetcher, Version, Reports +import re RUNNING = True @@ -143,6 +144,28 @@ class Monitor: self.session.commit() return _package + def _save_reports(self, analysis): + try: + reports = analysis.raw + report_dictionary = { + 'warnings': re.subn('warning', '', reports)[1], # get number of matches + 'styles': re.subn('style', '', reports)[1], + 'errors':re.subn('error', '', reports)[1] + } + for key, value in report_dictionary.items(): + _reports = kiskadee.model.Reports() + _reports.report_type = key + _reports.counter = value + _reports.analysis_id = analysis.id + self.session.add(_reports) + self.session.commit() + except Exception as err: + kiskadee.logger.debug( + "MONITOR: Failed to get analysis reports" + ) + kiskadee.logger.debug(err) + return + def _save_analysis(self, pkg, analyzer, result, version): _analysis = kiskadee.model.Analysis() try: @@ -153,6 +176,7 @@ class Monitor: _analysis.raw = json.loads(result) self.session.add(_analysis) self.session.commit() + self._save_reports(_analysis) kiskadee.logger.debug( "MONITOR: Saved analysis done by {} for package: {}_{}" .format(analyzer, pkg["name"], pkg["version"]) From f8629639d2ecf0a43100868a728f8df802deb8ef Mon Sep 17 00:00:00 2001 From: Vitor Borges Date: Oct 01 2017 20:41:26 +0000 Subject: [PATCH 2/6] [ANALYSIS] Added reports to analysis endpoint Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/api/app.py b/kiskadee/api/app.py index 37b3436..fd8b275 100644 --- a/kiskadee/api/app.py +++ b/kiskadee/api/app.py @@ -7,6 +7,7 @@ from kiskadee.database import Database from kiskadee.model import Package, Fetcher, Version, Analysis from kiskadee.api.serializers import PackageSchema, FetcherSchema,\ AnalysisSchema +import json kiskadee = Flask(__name__) @@ -56,6 +57,7 @@ def package_analysis(pkg_name, version): analysis_schema = AnalysisSchema() result = analysis_schema.dump(analysis) + result.data['raw'] = json.loads(result.data['raw']) return jsonify({'analysis': result.data}) diff --git a/kiskadee/api/serializers.py b/kiskadee/api/serializers.py index 401f0cf..db48660 100644 --- a/kiskadee/api/serializers.py +++ b/kiskadee/api/serializers.py @@ -1,7 +1,21 @@ """Provide objects to serialize the kiskadee models.""" from marshmallow import Schema, fields -from kiskadee.model import Package, Fetcher, Analysis, Version +from kiskadee.model import Package, Fetcher, Analysis, Version, Reports + + +class ReportsSchema(Schema): + """Provide a serializer to the Reports model.""" + + id = fields.Int() + analysis_id = fields.Int() + report_type = fields.Str() + counter = fields.Int() + + def make_object(self, data): + """Serialize a Reports object.""" + print('MAKING OBJECT FROM', data) + return Reports(**data) class AnalysisSchema(Schema): @@ -11,6 +25,7 @@ class AnalysisSchema(Schema): version_id = fields.Int() analyzer_id = fields.Int() raw = fields.Dict() + reports = fields.Nested(ReportsSchema, many=True) def make_object(self, data): """Serialize a Analysis object.""" diff --git a/kiskadee/model.py b/kiskadee/model.py index ee860db..81293f3 100644 --- a/kiskadee/model.py +++ b/kiskadee/model.py @@ -7,11 +7,6 @@ import kiskadee Base = declarative_base() -"""class TypeEnum(enum.Enum): - Enum for to use on TypeReport class - error = 1 - style = 2 - warning = 3""" class Package(Base): """Software packages abstraction. @@ -31,6 +26,7 @@ class Package(Base): UniqueConstraint('name', 'fetcher_id'), ) + class Fetcher(Base): """kiskadee fetcher abstraction.""" @@ -77,16 +73,21 @@ class Analysis(Base): version_id = Column(Integer, ForeignKey('versions.id'), nullable=False) analyzer_id = Column(Integer, ForeignKey('analyzers.id'), nullable=False) raw = Column(JSON) - report = orm.relationship('Reports', backref='analysis') + report = orm.relationship('Reports', uselist=False, back_populates='analysis') + class Reports(Base): - """Abstraction of a analysis report""" + """Abstraction of a analysis report.""" + __tablename__ = 'reports' id = Column(Integer, Sequence('reports_id_seq', optional=True), primary_key=True) - report_type = Column(Unicode(100), nullable=False) - counter = Column(Integer) + warnings = Column(Integer) + styles = Column(Integer) + errors = Column(Integer) analysis_id = Column(Integer, ForeignKey('analysis.id'), nullable=False) + analysis = orm.relationship('Analysis', back_populates='report') + def create_analyzers(_session): """Create the analyzers on database. diff --git a/kiskadee/monitor.py b/kiskadee/monitor.py index 6b84724..25aaa6a 100644 --- a/kiskadee/monitor.py +++ b/kiskadee/monitor.py @@ -12,7 +12,7 @@ import json import kiskadee.database from kiskadee.runner import Runner import kiskadee.queue -from kiskadee.model import Package, Fetcher, Version, Reports +from kiskadee.model import Package, Fetcher, Version import re RUNNING = True @@ -144,24 +144,29 @@ class Monitor: self.session.commit() return _package - def _save_reports(self, analysis): + def _save_reports(self, analysis, pkg): try: reports = analysis.raw report_dictionary = { - 'warnings': re.subn('warning', '', reports)[1], # get number of matches + 'warnings': re.subn('warning', '', reports)[1], 'styles': re.subn('style', '', reports)[1], - 'errors':re.subn('error', '', reports)[1] + 'errors': re.subn('error', '', reports)[1] } - for key, value in report_dictionary.items(): - _reports = kiskadee.model.Reports() - _reports.report_type = key - _reports.counter = value - _reports.analysis_id = analysis.id - self.session.add(_reports) - self.session.commit() + _reports = kiskadee.model.Reports() + _reports.warnings = report_dictionary['warnings'] + _reports.styles = report_dictionary['styles'] + _reports.errors = report_dictionary['errors'] + _reports.analysis_id = analysis.id + self.session.add(_reports) + self.session.commit() + kiskadee.logger.debug( + "MONITOR: Saved analysis reports for {} package" + .format(pkg["name"]) + ) except Exception as err: kiskadee.logger.debug( - "MONITOR: Failed to get analysis reports" + "MONITOR: Failed to get analysis reports to {} package" + .format(pkg["name"]) ) kiskadee.logger.debug(err) return @@ -176,7 +181,7 @@ class Monitor: _analysis.raw = json.loads(result) self.session.add(_analysis) self.session.commit() - self._save_reports(_analysis) + self._save_reports(_analysis, pkg) kiskadee.logger.debug( "MONITOR: Saved analysis done by {} for package: {}_{}" .format(analyzer, pkg["name"], pkg["version"]) From 7eee58b1ee375037edb27d2135798f262018bbf8 Mon Sep 17 00:00:00 2001 From: Vitor Borges Date: Oct 01 2017 20:41:27 +0000 Subject: [PATCH 3/6] [ENDPOINT] Changed return of `/analysis` endpoint to return array of analysis Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/api/app.py b/kiskadee/api/app.py index fd8b275..3ba1385 100644 --- a/kiskadee/api/app.py +++ b/kiskadee/api/app.py @@ -4,7 +4,7 @@ from flask import request from flask_cors import CORS from kiskadee.database import Database -from kiskadee.model import Package, Fetcher, Version, Analysis +from kiskadee.model import Package, Fetcher, Version, Analysis, Reports from kiskadee.api.serializers import PackageSchema, FetcherSchema,\ AnalysisSchema import json @@ -52,13 +52,13 @@ def package_analysis(pkg_name, version): ) analysis = ( db_session.query(Analysis) - .filter(Analysis.version_id == version_id).first() + .filter(Analysis.version_id == version_id).all() ) - - analysis_schema = AnalysisSchema() - result = analysis_schema.dump(analysis) - result.data['raw'] = json.loads(result.data['raw']) - return jsonify({'analysis': result.data}) + analysis_schema = AnalysisSchema(many=True) + results = analysis_schema.dump(analysis) + for data in results.data: + data['raw'] = json.loads(data['raw']) + return jsonify({'analysis': results.data}) def kiskadee_db_session(): diff --git a/kiskadee/api/serializers.py b/kiskadee/api/serializers.py index db48660..f39414a 100644 --- a/kiskadee/api/serializers.py +++ b/kiskadee/api/serializers.py @@ -9,8 +9,9 @@ class ReportsSchema(Schema): id = fields.Int() analysis_id = fields.Int() - report_type = fields.Str() - counter = fields.Int() + warnings = fields.Int() + styles = fields.Int() + errors = fields.Int() def make_object(self, data): """Serialize a Reports object.""" @@ -25,7 +26,7 @@ class AnalysisSchema(Schema): version_id = fields.Int() analyzer_id = fields.Int() raw = fields.Dict() - reports = fields.Nested(ReportsSchema, many=True) + report = fields.Nested(ReportsSchema) def make_object(self, data): """Serialize a Analysis object.""" From 68b206dc8e036fa506f9d5e3aa614bdb6a6ba135 Mon Sep 17 00:00:00 2001 From: Adailson Santos Date: Oct 01 2017 20:41:27 +0000 Subject: [PATCH 4/6] [TEST] Created tests cases for reports and /analysis endpoint - Added monitor test for save_reports method - Added unit tests for /analysis endpoint Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/api/app.py b/kiskadee/api/app.py index 3ba1385..ad1b580 100644 --- a/kiskadee/api/app.py +++ b/kiskadee/api/app.py @@ -4,7 +4,7 @@ from flask import request from flask_cors import CORS from kiskadee.database import Database -from kiskadee.model import Package, Fetcher, Version, Analysis, Reports +from kiskadee.model import Package, Fetcher, Version, Analysis from kiskadee.api.serializers import PackageSchema, FetcherSchema,\ AnalysisSchema import json diff --git a/kiskadee/model.py b/kiskadee/model.py index 81293f3..8bb6ed2 100644 --- a/kiskadee/model.py +++ b/kiskadee/model.py @@ -73,7 +73,8 @@ class Analysis(Base): version_id = Column(Integer, ForeignKey('versions.id'), nullable=False) analyzer_id = Column(Integer, ForeignKey('analyzers.id'), nullable=False) raw = Column(JSON) - report = orm.relationship('Reports', uselist=False, back_populates='analysis') + report = orm.relationship('Reports', + uselist=False, back_populates='analysis') class Reports(Base): @@ -82,10 +83,10 @@ class Reports(Base): __tablename__ = 'reports' id = Column(Integer, Sequence('reports_id_seq', optional=True), primary_key=True) + analysis_id = Column(Integer, ForeignKey('analysis.id'), nullable=False) warnings = Column(Integer) styles = Column(Integer) errors = Column(Integer) - analysis_id = Column(Integer, ForeignKey('analysis.id'), nullable=False) analysis = orm.relationship('Analysis', back_populates='report') diff --git a/kiskadee/monitor.py b/kiskadee/monitor.py index 25aaa6a..ba66e72 100644 --- a/kiskadee/monitor.py +++ b/kiskadee/monitor.py @@ -146,7 +146,7 @@ class Monitor: def _save_reports(self, analysis, pkg): try: - reports = analysis.raw + reports = analysis['raw'] report_dictionary = { 'warnings': re.subn('warning', '', reports)[1], 'styles': re.subn('style', '', reports)[1], @@ -156,7 +156,7 @@ class Monitor: _reports.warnings = report_dictionary['warnings'] _reports.styles = report_dictionary['styles'] _reports.errors = report_dictionary['errors'] - _reports.analysis_id = analysis.id + _reports.analysis_id = analysis['id'] self.session.add(_reports) self.session.commit() kiskadee.logger.debug( diff --git a/kiskadee/tests/test_api.py b/kiskadee/tests/test_api.py index 916aebf..075d297 100644 --- a/kiskadee/tests/test_api.py +++ b/kiskadee/tests/test_api.py @@ -21,7 +21,29 @@ class ApiTestCase(unittest.TestCase): fetcher = kiskadee.model.Fetcher( name='kiskadee-fetcher', target='university' ) + model.Base.metadata.create_all(self.engine) + model.create_analyzers(self.session) + fetcher = kiskadee.model.Fetcher( + name='kiskadee-fetcher', target='university', id=1 + ) + pkg = kiskadee.model.Package( + name='kiskadee-package', fetcher_id=1, id=1 + ) + version = kiskadee.model.Version( + number='7.23', package_id=1, id=1 + ) + analysis = kiskadee.model.Analysis( + version_id=1, analyzer_id=1, + raw="{\"metadata\": \"test\"}" + ) + report = model.Reports( + analysis_id=1, warnings=1, styles=1, errors=1 + ) self.session.add(fetcher) + self.session.add(pkg) + self.session.add(version) + self.session.add(analysis) + self.session.add(report) self.session.commit() self.runner = Runner() self.monitor = Monitor(self.session) @@ -73,6 +95,19 @@ class ApiTestCase(unittest.TestCase): self.assertIn('cwe', pkg_first_analysis) self.assertIn('message', pkg_first_analysis) + def test_get_specific_analysis(self): + + def mock_kiskadee_db_session(): + return self.session + + kiskadee.api.app.kiskadee_db_session = mock_kiskadee_db_session + response = self.app.get("/analysis/kiskadee-package/7.23/") + response_as_json = json.loads(response.get_data(as_text=True)) + self.assertIn("analysis", response_as_json) + # On the first analysis of response, + # check if exists some report attribute + self.assertIn("report", response_as_json['analysis'][0]) + if __name__ == '__main__': unittest.main() diff --git a/kiskadee/tests/test_model.py b/kiskadee/tests/test_model.py index 0a6b7f0..b1445cc 100644 --- a/kiskadee/tests/test_model.py +++ b/kiskadee/tests/test_model.py @@ -15,7 +15,7 @@ class ModelTestCase(unittest.TestCase): model.Base.metadata.create_all(self.engine) model.create_analyzers(self.session) self.fetcher = model.Fetcher( - name='kiskadee-fetcher', target='university' + name='kiskadee-fetcher', target='university' ) self.package = model.Package(name='python-kiskadee') self.version = model.Version(number='1.0-rc1') @@ -27,9 +27,17 @@ class ModelTestCase(unittest.TestCase): self.analysis = model.Analysis( analyzer_id=1, - version_id=self.version.id, + version_id=1, raw="" ) + self.session.add(self.analysis) + self.report = model.Reports( + analysis_id=1, + warnings=0, + styles=0, + errors=0 + ) + self.session.add(self.report) self.session.commit() def tearDown(self): @@ -48,6 +56,10 @@ class ModelTestCase(unittest.TestCase): versions = self.session.query(model.Version).all() self.assertEqual(versions, [self.version]) + def test_query_report(self): + reports = self.session.query(model.Reports).all() + self.assertEqual(reports, [self.report]) + def test_add_fetcher(self): fetchers = self.session.query(model.Fetcher).all() self.assertEqual(len(fetchers), 1) @@ -61,6 +73,16 @@ class ModelTestCase(unittest.TestCase): with self.assertRaises(exc.IntegrityError): self.session.commit() + def test_add_report_without_analysis(self): + report = model.Reports( + warnings=0, + styles=0, + errors=0 + ) + self.session.add(report) + with self.assertRaises(exc.IntegrityError): + self.session.commit() + def test_add_package_without_fetcher(self): package = model.Package(name='foo-bar') self.session.add(package) diff --git a/kiskadee/tests/test_monitor.py b/kiskadee/tests/test_monitor.py index 2d6b70f..ac4a452 100644 --- a/kiskadee/tests/test_monitor.py +++ b/kiskadee/tests/test_monitor.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import sessionmaker from kiskadee import model from kiskadee.monitor import Monitor from kiskadee.queue import packages_queue -from kiskadee.model import Package, Fetcher, create_analyzers +from kiskadee.model import Package, Fetcher, create_analyzers, Reports import kiskadee.queue import kiskadee.fetchers.debian from kiskadee.database import Database @@ -46,6 +46,9 @@ class MonitorTestCase(unittest.TestCase): 'cppcheck': '<>', 'flawfinder': '><'}, 'fetcher_id': 1} + self.analysis = {'analyzer_id': 1, + 'id': 1, + 'raw': 'warning, style, error'} def tearDown(self): self.session.close() @@ -86,6 +89,15 @@ class MonitorTestCase(unittest.TestCase): self.assertEqual(len(_pkgs), 2) self.assertEqual(_pkgs[1].name, _pkg['name']) + def test_save_reports(self): + self.monitor._save_reports(self.analysis, self.pkg1) + _report = self.monitor.session.query(Reports).all() + self.assertEqual(len(_report), 1) + self.assertEqual(_report[0].analysis_id, 1) + self.assertEqual(_report[0].warnings, 1) + self.assertEqual(_report[0].styles, 1) + self.assertEqual(_report[0].errors, 1) + def test_save_version(self): self.monitor._save_fetcher(kiskadee.fetchers.debian.Fetcher()) self.monitor._save_analyzed_pkg(self.pkg1) From ef3a208dfa05e936b41c410bb14920b771ac0afd Mon Sep 17 00:00:00 2001 From: Vitor Borges Date: Oct 01 2017 20:41:48 +0000 Subject: [PATCH 5/6] Changed the Report format on database Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/api/app.py b/kiskadee/api/app.py index ad1b580..9860ebb 100644 --- a/kiskadee/api/app.py +++ b/kiskadee/api/app.py @@ -56,8 +56,6 @@ def package_analysis(pkg_name, version): ) analysis_schema = AnalysisSchema(many=True) results = analysis_schema.dump(analysis) - for data in results.data: - data['raw'] = json.loads(data['raw']) return jsonify({'analysis': results.data}) diff --git a/kiskadee/api/serializers.py b/kiskadee/api/serializers.py index f39414a..98e7835 100644 --- a/kiskadee/api/serializers.py +++ b/kiskadee/api/serializers.py @@ -1,7 +1,7 @@ """Provide objects to serialize the kiskadee models.""" from marshmallow import Schema, fields -from kiskadee.model import Package, Fetcher, Analysis, Version, Reports +from kiskadee.model import Package, Fetcher, Analysis, Version, Report class ReportsSchema(Schema): @@ -9,14 +9,12 @@ class ReportsSchema(Schema): id = fields.Int() analysis_id = fields.Int() - warnings = fields.Int() - styles = fields.Int() - errors = fields.Int() + results = fields.Dict() def make_object(self, data): """Serialize a Reports object.""" print('MAKING OBJECT FROM', data) - return Reports(**data) + return Report(**data) class AnalysisSchema(Schema): diff --git a/kiskadee/model.py b/kiskadee/model.py index 8bb6ed2..81fe42c 100644 --- a/kiskadee/model.py +++ b/kiskadee/model.py @@ -73,20 +73,18 @@ class Analysis(Base): version_id = Column(Integer, ForeignKey('versions.id'), nullable=False) analyzer_id = Column(Integer, ForeignKey('analyzers.id'), nullable=False) raw = Column(JSON) - report = orm.relationship('Reports', + report = orm.relationship('Report', uselist=False, back_populates='analysis') -class Reports(Base): +class Report(Base): """Abstraction of a analysis report.""" __tablename__ = 'reports' id = Column(Integer, Sequence('reports_id_seq', optional=True), primary_key=True) analysis_id = Column(Integer, ForeignKey('analysis.id'), nullable=False) - warnings = Column(Integer) - styles = Column(Integer) - errors = Column(Integer) + results = Column(JSON) analysis = orm.relationship('Analysis', back_populates='report') diff --git a/kiskadee/monitor.py b/kiskadee/monitor.py index ba66e72..1039b74 100644 --- a/kiskadee/monitor.py +++ b/kiskadee/monitor.py @@ -10,13 +10,18 @@ import os import json import kiskadee.database +from kiskadee.report import CppCheckReport from kiskadee.runner import Runner import kiskadee.queue -from kiskadee.model import Package, Fetcher, Version +from kiskadee.model import Package, Fetcher, Version, Report import re RUNNING = True +REPORTERS = { + 'cppcheck': CppCheckReport +} + class Monitor: """Provide kiskadee monitoring objects.""" @@ -144,19 +149,13 @@ class Monitor: self.session.commit() return _package - def _save_reports(self, analysis, pkg): + def _save_reports(self, analysis, pkg, analyzer_name): try: - reports = analysis['raw'] - report_dictionary = { - 'warnings': re.subn('warning', '', reports)[1], - 'styles': re.subn('style', '', reports)[1], - 'errors': re.subn('error', '', reports)[1] - } - _reports = kiskadee.model.Reports() - _reports.warnings = report_dictionary['warnings'] - _reports.styles = report_dictionary['styles'] - _reports.errors = report_dictionary['errors'] - _reports.analysis_id = analysis['id'] + results = analysis.raw['results'] + analyzer_report = REPORTERS[analyzer_name](results) + _reports = Report() + _reports.results = json.dumps(analyzer_report.compute_reports()) + _reports.analysis_id = analysis.id self.session.add(_reports) self.session.commit() kiskadee.logger.debug( @@ -181,7 +180,7 @@ class Monitor: _analysis.raw = json.loads(result) self.session.add(_analysis) self.session.commit() - self._save_reports(_analysis, pkg) + self._save_reports(_analysis, pkg, _analyzer.name) kiskadee.logger.debug( "MONITOR: Saved analysis done by {} for package: {}_{}" .format(analyzer, pkg["name"], pkg["version"]) diff --git a/kiskadee/report.py b/kiskadee/report.py new file mode 100644 index 0000000..273f339 --- /dev/null +++ b/kiskadee/report.py @@ -0,0 +1,29 @@ +from abc import ABCMeta, abstractmethod + +class Report: + __metaclass__ = ABCMeta + + def __init__(self, results): + self.results = results + + @abstractmethod + def compute_reports(self, results): pass + + +class CppCheckReport(Report): + + def compute_reports(self): + result_dict = { + 'warning': 0, + 'error': 0, + 'style': 0 + } + for result in self.results: + if 'severity' in list(result.keys()): + result_dict[result['severity']] += 1 + else: + print('deu ruimmmm') + + print(result_dict) + return result_dict + From 5e2a0d0bf4aec07209a7dfa4b42cb4693b5c3d2b Mon Sep 17 00:00:00 2001 From: Vitor Borges Date: Oct 01 2017 20:41:49 +0000 Subject: [PATCH 6/6] [REPORTS] Added another report class - Added flawfinder concrete reporter - Added report tests Signed-off-by: Adailson Santos Signed-off-by: Vitor Borges --- diff --git a/kiskadee/api/app.py b/kiskadee/api/app.py index 9860ebb..4acdb24 100644 --- a/kiskadee/api/app.py +++ b/kiskadee/api/app.py @@ -56,6 +56,11 @@ def package_analysis(pkg_name, version): ) analysis_schema = AnalysisSchema(many=True) results = analysis_schema.dump(analysis) + for result in results.data: + report = result['report'] + if (report is not None) and ('results' in report.keys()): + report['results'] = json\ + .loads(report['results']) return jsonify({'analysis': results.data}) diff --git a/kiskadee/monitor.py b/kiskadee/monitor.py index 1039b74..b8694cb 100644 --- a/kiskadee/monitor.py +++ b/kiskadee/monitor.py @@ -10,16 +10,16 @@ import os import json import kiskadee.database -from kiskadee.report import CppCheckReport +from kiskadee.report import CppcheckReport, FlawfinderReport from kiskadee.runner import Runner import kiskadee.queue from kiskadee.model import Package, Fetcher, Version, Report -import re RUNNING = True REPORTERS = { - 'cppcheck': CppCheckReport + 'cppcheck': CppcheckReport, + 'flawfinder': FlawfinderReport } @@ -151,17 +151,26 @@ class Monitor: def _save_reports(self, analysis, pkg, analyzer_name): try: - results = analysis.raw['results'] + results = analysis['results'] analyzer_report = REPORTERS[analyzer_name](results) _reports = Report() - _reports.results = json.dumps(analyzer_report.compute_reports()) - _reports.analysis_id = analysis.id + _reports.results = json.dumps( + analyzer_report + ._compute_reports(analyzer_name) + ) + _reports.analysis_id = analysis['id'] self.session.add(_reports) self.session.commit() kiskadee.logger.debug( "MONITOR: Saved analysis reports for {} package" .format(pkg["name"]) ) + except KeyError as key: + kiskadee.logger.debug( + "ERROR: There's no reporter " + + "to get reports from {} analyzer. ".format(key) + + "Make shure to import or implement them." + ) except Exception as err: kiskadee.logger.debug( "MONITOR: Failed to get analysis reports to {} package" @@ -180,14 +189,19 @@ class Monitor: _analysis.raw = json.loads(result) self.session.add(_analysis) self.session.commit() - self._save_reports(_analysis, pkg, _analyzer.name) + dict_analysis = { + 'results': _analysis.raw['results'], + 'id': _analysis.id + } + self._save_reports(dict_analysis, pkg, _analyzer.name) kiskadee.logger.debug( "MONITOR: Saved analysis done by {} for package: {}_{}" .format(analyzer, pkg["name"], pkg["version"]) ) + return except Exception as err: kiskadee.logger.debug( - "MONITOR: The required analyzer was" + + "MONITOR: The required analyzer was " + "not registered in kiskadee" ) kiskadee.logger.debug(err) diff --git a/kiskadee/report.py b/kiskadee/report.py index 273f339..10aa7cc 100644 --- a/kiskadee/report.py +++ b/kiskadee/report.py @@ -1,29 +1,80 @@ +"""Provide reporter capabilities to kiskadee analyzers. + +Every analyzer on kiskadee has differents reports results. +This module came to compute and deal with this differences. +""" +import kiskadee from abc import ABCMeta, abstractmethod + class Report: + """Abstraction of a analyzer reporter.""" + __metaclass__ = ABCMeta def __init__(self, results): + """Initialize Report class.""" self.results = results @abstractmethod - def compute_reports(self, results): pass + def _compute_reports(self, results): pass + + @staticmethod + def logger_message(errors, analyzer): + """Logger of reporter computer methods.""" + kiskadee.logger.debug( + "WARNING: There are " + + "{} registers on JSON ".format(errors) + + "that was not able to convert to " + + "'{}' reports.".format(analyzer) + ) + return -class CppCheckReport(Report): +class CppcheckReport(Report): + """Concrete reporter implementation to cppcheck analyzer.""" - def compute_reports(self): + def _compute_reports(self, analyzer): + """Compute every report type for cppcheck analyzer.""" + print(analyzer) result_dict = { 'warning': 0, 'error': 0, 'style': 0 } + count_error = 0 for result in self.results: if 'severity' in list(result.keys()): result_dict[result['severity']] += 1 else: - print('deu ruimmmm') + count_error += 1 + + if count_error > 0: + self.logger_message(count_error, analyzer) - print(result_dict) return result_dict + +class FlawfinderReport(Report): + """Concrete reporter implementation to flawfinder analyzer.""" + + def _compute_reports(self, analyzer): + """Compute every report type for flawfinder analyzer.""" + result_dict = { + 'severity_1': 0, + 'severity_2': 0, + 'severity_3': 0, + 'severity_4': 0, + 'severity_5': 0 + } + count_error = 0 + for result in self.results: + if 'severity' in list(result.keys()): + result_dict['severity_' + result['severity']] += 1 + else: + count_error += 1 + + if count_error > 0: + self.logger_message(count_error, analyzer) + + return result_dict diff --git a/kiskadee/tests/test_api.py b/kiskadee/tests/test_api.py index 075d297..d3714c1 100644 --- a/kiskadee/tests/test_api.py +++ b/kiskadee/tests/test_api.py @@ -18,26 +18,29 @@ class ApiTestCase(unittest.TestCase): self.session = Session() self.app = kiskadee.api.app.kiskadee.test_client() kiskadee.model.create_analyzers(self.session) + kiskadee.model.Base.metadata.create_all(self.engine) + kiskadee.model.create_analyzers(self.session) fetcher = kiskadee.model.Fetcher( name='kiskadee-fetcher', target='university' ) - model.Base.metadata.create_all(self.engine) - model.create_analyzers(self.session) - fetcher = kiskadee.model.Fetcher( - name='kiskadee-fetcher', target='university', id=1 - ) pkg = kiskadee.model.Package( - name='kiskadee-package', fetcher_id=1, id=1 + name='kiskadee-package', fetcher_id=1 ) version = kiskadee.model.Version( - number='7.23', package_id=1, id=1 + number='7.23', package_id=1 ) analysis = kiskadee.model.Analysis( version_id=1, analyzer_id=1, - raw="{\"metadata\": \"test\"}" + raw={ + 'results': [ + {'severity': 'warning'}, + {'severity': 'style'}, + {'severity': 'error'} + ] + } ) - report = model.Reports( - analysis_id=1, warnings=1, styles=1, errors=1 + report = kiskadee.model.Report( + analysis_id=1 ) self.session.add(fetcher) self.session.add(pkg) @@ -89,24 +92,12 @@ class ApiTestCase(unittest.TestCase): kiskadee.api.app.kiskadee_db_session = mock_kiskadee_db_session response = self.app.get("/analysis/test/1.0.0") response_data = json.loads(response.data.decode("utf-8")) - self.assertIsNotNone(response_data["analysis"]["raw"]) - pkg_first_analysis = response_data["analysis"]["raw"]["results"][0] - self.assertIn('location', pkg_first_analysis) - self.assertIn('cwe', pkg_first_analysis) - self.assertIn('message', pkg_first_analysis) - - def test_get_specific_analysis(self): - - def mock_kiskadee_db_session(): - return self.session - - kiskadee.api.app.kiskadee_db_session = mock_kiskadee_db_session - response = self.app.get("/analysis/kiskadee-package/7.23/") - response_as_json = json.loads(response.get_data(as_text=True)) - self.assertIn("analysis", response_as_json) - # On the first analysis of response, - # check if exists some report attribute - self.assertIn("report", response_as_json['analysis'][0]) + pkg_first_analysis = response_data["analysis"][0] + self.assertIsNotNone(pkg_first_analysis["raw"]) + self.assertIn('report', pkg_first_analysis) + self.assertIn('location', pkg_first_analysis["raw"]["results"][0]) + self.assertIn('cwe', pkg_first_analysis["raw"]["results"][0]) + self.assertIn('message', pkg_first_analysis["raw"]["results"][0]) if __name__ == '__main__': diff --git a/kiskadee/tests/test_model.py b/kiskadee/tests/test_model.py index b1445cc..4a56835 100644 --- a/kiskadee/tests/test_model.py +++ b/kiskadee/tests/test_model.py @@ -31,11 +31,8 @@ class ModelTestCase(unittest.TestCase): raw="" ) self.session.add(self.analysis) - self.report = model.Reports( + self.report = model.Report( analysis_id=1, - warnings=0, - styles=0, - errors=0 ) self.session.add(self.report) self.session.commit() @@ -57,7 +54,7 @@ class ModelTestCase(unittest.TestCase): self.assertEqual(versions, [self.version]) def test_query_report(self): - reports = self.session.query(model.Reports).all() + reports = self.session.query(model.Report).all() self.assertEqual(reports, [self.report]) def test_add_fetcher(self): @@ -74,10 +71,7 @@ class ModelTestCase(unittest.TestCase): self.session.commit() def test_add_report_without_analysis(self): - report = model.Reports( - warnings=0, - styles=0, - errors=0 + report = model.Report( ) self.session.add(report) with self.assertRaises(exc.IntegrityError): diff --git a/kiskadee/tests/test_monitor.py b/kiskadee/tests/test_monitor.py index ac4a452..57512b4 100644 --- a/kiskadee/tests/test_monitor.py +++ b/kiskadee/tests/test_monitor.py @@ -4,7 +4,7 @@ from sqlalchemy.orm import sessionmaker from kiskadee import model from kiskadee.monitor import Monitor from kiskadee.queue import packages_queue -from kiskadee.model import Package, Fetcher, create_analyzers, Reports +from kiskadee.model import Package, Fetcher, create_analyzers, Report, Analysis import kiskadee.queue import kiskadee.fetchers.debian from kiskadee.database import Database @@ -46,9 +46,17 @@ class MonitorTestCase(unittest.TestCase): 'cppcheck': '<>', 'flawfinder': '><'}, 'fetcher_id': 1} - self.analysis = {'analyzer_id': 1, - 'id': 1, - 'raw': 'warning, style, error'} + self.analysis = { + 'analyzer_id': 1, + 'id': 1, + 'raw': { + 'results': [ + {'severity': 'warning'}, + {'severity': 'style'}, + {'severity': 'error'} + ] + } + } def tearDown(self): self.session.close() @@ -90,13 +98,40 @@ class MonitorTestCase(unittest.TestCase): self.assertEqual(_pkgs[1].name, _pkg['name']) def test_save_reports(self): - self.monitor._save_reports(self.analysis, self.pkg1) - _report = self.monitor.session.query(Reports).all() + _fetcher = model.Fetcher( + name='kiskadee-fetcher', target='university' + ) + _pkg = model.Package( + name='kiskadee-package', fetcher_id=1 + ) + _version = model.Version( + number='1.0-rc1', package_id=1 + ) + _raw = { + 'results': [ + {'severity': 'warning'}, + {'severity': 'style'}, + {'severity': 'error'} + ] + } + _analysis = Analysis( + version_id=1, + analyzer_id=1, + id=1, + raw=_raw + ) + self.session.add(_fetcher) + self.session.add(_pkg) + self.session.add(_version) + self.session.add(_analysis) + self.session.commit() + _dict_analysis = { + 'results': _raw['results'], + 'id': 1 + } + self.monitor._save_reports(_dict_analysis, self.pkg1, 'cppcheck') + _report = self.monitor.session.query(Report).all() self.assertEqual(len(_report), 1) - self.assertEqual(_report[0].analysis_id, 1) - self.assertEqual(_report[0].warnings, 1) - self.assertEqual(_report[0].styles, 1) - self.assertEqual(_report[0].errors, 1) def test_save_version(self): self.monitor._save_fetcher(kiskadee.fetchers.debian.Fetcher()) diff --git a/kiskadee/tests/test_report.py b/kiskadee/tests/test_report.py new file mode 100644 index 0000000..10cb936 --- /dev/null +++ b/kiskadee/tests/test_report.py @@ -0,0 +1,50 @@ +import unittest + +from sqlalchemy.orm import sessionmaker +from kiskadee.report import CppcheckReport, FlawfinderReport +from kiskadee.database import Database + + +class ReportTestCase(unittest.TestCase): + + def setUp(self): + self.engine = Database('db_test').engine + Session = sessionmaker(bind=self.engine) + self.session = Session() + + def tearDown(self): + self.session.close() + + def test_compute_cppcheck_reports(self): + _reports = [ + {'severity': 'warning'}, + {'severity': 'error'}, + {'severity': 'style'}, + {'some-attribute': None} + ] + _cpp_reporter = CppcheckReport(_reports) + result = _cpp_reporter._compute_reports('cppcheck') + self.assertEqual(len(result.keys()), 3) + self.assertEqual(result['warning'], 1) + self.assertEqual(result['style'], 1) + self.assertEqual(result['error'], 1) + + def test_compute_flawfinder_reports(self): + _reports = [ + {'severity': '5'}, + {'severity': '4'}, + {'severity': '3'}, + {'some-attribute': None} + ] + _flawfinder_reporter = FlawfinderReport(_reports) + result = _flawfinder_reporter._compute_reports('flawfinder') + self.assertEqual(len(result.keys()), 5) + self.assertEqual(result['severity_5'], 1) + self.assertEqual(result['severity_4'], 1) + self.assertEqual(result['severity_3'], 1) + self.assertEqual(result['severity_2'], 0) + self.assertEqual(result['severity_1'], 0) + + +if __name__ == '__main__': + unittest.main()