Add files table
This commit adds 'files' table. This files table has coverage_id column as a foreign key for the coverages table. And this commit also adds a release note for it. Change-Id: I8998a79a1ba79bbdab1cd79810cc85bcbccbe7d8
This commit is contained in:
parent
ff3bbb9766
commit
856a9739da
|
@ -70,8 +70,8 @@ def get_session(autocommit=True, expire_on_commit=False):
|
||||||
return session
|
return session
|
||||||
|
|
||||||
|
|
||||||
def create_coverage(project_name, coverage_rate=0.0, report_time=None,
|
def create_coverage(project_name, coverage_rate=0.0, rates=[],
|
||||||
test_type='py27', session=None):
|
report_time=None, test_type='py27', session=None):
|
||||||
"""Create a new coverage record in the database.
|
"""Create a new coverage record in the database.
|
||||||
|
|
||||||
This method is used to add a new coverage in the database.
|
This method is used to add a new coverage in the database.
|
||||||
|
@ -98,12 +98,41 @@ def create_coverage(project_name, coverage_rate=0.0, report_time=None,
|
||||||
report_time_microsecond = None
|
report_time_microsecond = None
|
||||||
coverage.report_time = report_time
|
coverage.report_time = report_time
|
||||||
coverage.report_time_microsecond = report_time_microsecond
|
coverage.report_time_microsecond = report_time_microsecond
|
||||||
|
|
||||||
session = session or get_session()
|
session = session or get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
session.add(coverage)
|
session.add(coverage)
|
||||||
|
|
||||||
return coverage
|
return coverage
|
||||||
|
|
||||||
|
|
||||||
|
def add_file_rates(coverage_id, rates=[], session=None):
|
||||||
|
"""Add rates a specific coverage.
|
||||||
|
|
||||||
|
This method is used to add rate records in the database.
|
||||||
|
It tracks the coverage history by individual files.
|
||||||
|
|
||||||
|
:param int coverage_id: coverage_id
|
||||||
|
:param list rates: rates dict list which has
|
||||||
|
e.g. [{'filename': 'foo', 'line-rate': '0.1'}, ...]
|
||||||
|
:param session: optional session object if one isn't provided a new session
|
||||||
|
will be acquired for the duration of this operation
|
||||||
|
:return: The list of created files objects
|
||||||
|
:rtype: coverage2sql.models.File
|
||||||
|
"""
|
||||||
|
session = session or get_session()
|
||||||
|
files = []
|
||||||
|
with session.begin():
|
||||||
|
for r in rates:
|
||||||
|
f = models.File()
|
||||||
|
f.coverage_id = coverage_id
|
||||||
|
f.filename = r['filename']
|
||||||
|
f.line_rate = r['line-rate']
|
||||||
|
session.add(f)
|
||||||
|
files.append(f)
|
||||||
|
return files
|
||||||
|
|
||||||
|
|
||||||
def get_coverage(project_name=None, test_type=None, session=None):
|
def get_coverage(project_name=None, test_type=None, session=None):
|
||||||
"""Get new coverage records in the database.
|
"""Get new coverage records in the database.
|
||||||
|
|
||||||
|
|
|
@ -54,3 +54,17 @@ class Coverage(BASE, CoverageBase):
|
||||||
test_type = sa.Column(sa.String(256), nullable=False, default='py27')
|
test_type = sa.Column(sa.String(256), nullable=False, default='py27')
|
||||||
report_time = sa.Column(sa.DateTime(), default=datetime.datetime.utcnow())
|
report_time = sa.Column(sa.DateTime(), default=datetime.datetime.utcnow())
|
||||||
report_time_microsecond = sa.Column(sa.Integer(), default=0)
|
report_time_microsecond = sa.Column(sa.Integer(), default=0)
|
||||||
|
|
||||||
|
|
||||||
|
class File(BASE, CoverageBase):
|
||||||
|
__tablename__ = 'files'
|
||||||
|
__table_args__ = (sa.Index('ix_file_coverage_id', 'coverage_id'),
|
||||||
|
sa.Index('ix_filename', 'filename'))
|
||||||
|
id = sa.Column(sa.BigInteger, primary_key=True)
|
||||||
|
coverage_id = sa.Column(sa.BigInteger, nullable=False)
|
||||||
|
filename = sa.Column(sa.String(256), nullable=False)
|
||||||
|
line_rate = sa.Column(sa.Float())
|
||||||
|
coverage = sa.orm.relationship(Coverage,
|
||||||
|
backref=sa.orm.backref('file_coverage'),
|
||||||
|
foreign_keys=coverage_id,
|
||||||
|
primaryjoin=coverage_id == Coverage.id)
|
||||||
|
|
|
@ -52,4 +52,4 @@ def upgrade():
|
||||||
|
|
||||||
|
|
||||||
def downgrade():
|
def downgrade():
|
||||||
raise NotImplementedError()
|
op.drop_table('classes')
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Copyright (c) 2017 Hewlett Packard Enterprise Development LP
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
# implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
"""Add files table
|
||||||
|
|
||||||
|
Revision ID: 79dead6f7c26
|
||||||
|
Revises: cb0e61ce633e
|
||||||
|
Create Date: 2017-02-07 17:57:28.777311
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '79dead6f7c26'
|
||||||
|
down_revision = 'cb0e61ce633e'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
from alembic import context
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
migration_context = context.get_context()
|
||||||
|
if migration_context.dialect.name == 'sqlite':
|
||||||
|
id_type = sa.Integer
|
||||||
|
else:
|
||||||
|
id_type = sa.BigInteger
|
||||||
|
|
||||||
|
op.create_table('files',
|
||||||
|
sa.Column('id', id_type, autoincrement=True,
|
||||||
|
primary_key=True),
|
||||||
|
sa.Column('coverage_id', id_type, nullable=False),
|
||||||
|
sa.Column('filename', sa.String(256), nullable=False),
|
||||||
|
sa.Column('line_rate', sa.Float()),
|
||||||
|
mysql_engine='InnoDB')
|
||||||
|
op.create_index('ix_class_coverage_id', 'files', ['coverage_id'])
|
||||||
|
op.create_index('ix_filename', 'files', ['filename'])
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.drop_table('files')
|
|
@ -12,7 +12,10 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import tempfile
|
||||||
|
|
||||||
import coverage
|
import coverage
|
||||||
|
import xmltodict
|
||||||
|
|
||||||
|
|
||||||
class DevNull(object):
|
class DevNull(object):
|
||||||
|
@ -26,10 +29,30 @@ class ReadCoverage(object):
|
||||||
def __init__(self, coverage_file=None):
|
def __init__(self, coverage_file=None):
|
||||||
self.cov = coverage.Coverage(data_file=coverage_file)
|
self.cov = coverage.Coverage(data_file=coverage_file)
|
||||||
self.cov.load()
|
self.cov.load()
|
||||||
self.cov_pct = self.cov.report(file=DevNull())
|
xmlfile = tempfile.NamedTemporaryFile(suffix='.xml')
|
||||||
|
self.cov_pct = self.cov.xml_report(outfile=xmlfile.name)
|
||||||
|
self.covdoc = xmltodict.parse(xmlfile.read())
|
||||||
|
xmlfile.close()
|
||||||
|
|
||||||
def get_data(self):
|
def get_data(self):
|
||||||
return self.cov.get_data()
|
return self.cov.get_data()
|
||||||
|
|
||||||
def get_coverage_rate(self):
|
def get_coverage_rate(self):
|
||||||
return self.cov_pct / 100
|
return self.cov_pct / 100
|
||||||
|
|
||||||
|
def get_rates_by_files(self):
|
||||||
|
rates = []
|
||||||
|
for p in self.covdoc['coverage']['packages']['package']:
|
||||||
|
try:
|
||||||
|
# FIXME(masayukig): Try to access the first element. This is
|
||||||
|
# ugly..
|
||||||
|
p['classes']['class'][0]
|
||||||
|
for c in p['classes']['class']:
|
||||||
|
rates.append({'filename': c['@filename'],
|
||||||
|
'line-rate': c['@line-rate']})
|
||||||
|
except KeyError:
|
||||||
|
# NOTE(masayukig): This has only one class
|
||||||
|
c = p['classes']['class']
|
||||||
|
rates.append({'filename': c['@filename'],
|
||||||
|
'line-rate': c['@line-rate']})
|
||||||
|
return rates
|
||||||
|
|
|
@ -62,9 +62,11 @@ def parse_args(argv, default_config_files=None):
|
||||||
default_config_files=default_config_files)
|
default_config_files=default_config_files)
|
||||||
|
|
||||||
|
|
||||||
def process_results(project_name=".", coverage_rate=0.0):
|
def process_results(project_name=".", coverage_rate=0.0, rates=[]):
|
||||||
session = api.get_session()
|
session = api.get_session()
|
||||||
api.create_coverage(project_name, coverage_rate, test_type=CONF.test_type)
|
cov = api.create_coverage(project_name, coverage_rate, rates,
|
||||||
|
test_type=CONF.test_type, session=session)
|
||||||
|
api.add_file_rates(cov.id, rates, session)
|
||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
|
|
||||||
|
@ -76,9 +78,10 @@ def main():
|
||||||
if CONF.coverage_file:
|
if CONF.coverage_file:
|
||||||
cov = coverage.ReadCoverage(CONF.coverage_file)
|
cov = coverage.ReadCoverage(CONF.coverage_file)
|
||||||
coverage_rate = cov.get_coverage_rate()
|
coverage_rate = cov.get_coverage_rate()
|
||||||
|
rates = cov.get_rates_by_files()
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
process_results(project_name, coverage_rate)
|
process_results(project_name, coverage_rate, rates)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -65,3 +65,14 @@ class TestDatabaseAPI(base.TestCase):
|
||||||
self.assertTrue(covs is not None)
|
self.assertTrue(covs is not None)
|
||||||
self.assertEqual(len(covs), 1)
|
self.assertEqual(len(covs), 1)
|
||||||
self.assertEqual(covs[0].project_name, 'foo1_project')
|
self.assertEqual(covs[0].project_name, 'foo1_project')
|
||||||
|
|
||||||
|
def test_add_file_rates(self):
|
||||||
|
rates = []
|
||||||
|
rates.append({'filename': 'foo/bar0', 'line-rate': '0'})
|
||||||
|
rates.append({'filename': 'foo/bar1', 'line-rate': '1'})
|
||||||
|
rates.append({'filename': 'foo/bar2', 'line-rate': '0.92'})
|
||||||
|
files = api.add_file_rates(1, rates)
|
||||||
|
self.assertEqual(3, len(files))
|
||||||
|
for r, f in zip(rates, files):
|
||||||
|
self.assertEqual(r['filename'], f.filename)
|
||||||
|
self.assertEqual(r['line-rate'], f.line_rate)
|
||||||
|
|
|
@ -44,6 +44,10 @@ class TestMain(base.TestCase):
|
||||||
'get_coverage_rate')
|
'get_coverage_rate')
|
||||||
fake_read_coverage.get_coverage_rate.return_value = (
|
fake_read_coverage.get_coverage_rate.return_value = (
|
||||||
fake_get_coverage_rate)
|
fake_get_coverage_rate)
|
||||||
|
fake_read_coverage.get_rates_by_files = mock.MagicMock(
|
||||||
|
'get_rates_by_files')
|
||||||
|
fake_read_coverage.get_rates_by_files.return_value = (
|
||||||
|
{'filename': 'foo/bar.py', 'line-rate': '0.99'})
|
||||||
read_coverage_mock.return_value = fake_read_coverage
|
read_coverage_mock.return_value = fake_read_coverage
|
||||||
shell.main()
|
shell.main()
|
||||||
read_coverage_mock.assert_called_with(mock.ANY)
|
read_coverage_mock.assert_called_with(mock.ANY)
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Add 'Files' table to store individual coverage data by file.
|
|
@ -1,14 +1,13 @@
|
||||||
Welcome to Coverage2sql Release Notes documentation!
|
Welcome to Coverage2sql Release Notes documentation!
|
||||||
===================================================
|
====================================================
|
||||||
|
|
||||||
Contents
|
Contents
|
||||||
========
|
========
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 2
|
:maxdepth: 1
|
||||||
|
|
||||||
unreleased
|
|
||||||
|
|
||||||
|
unreleased
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -6,3 +6,5 @@ pbr>=1.6
|
||||||
SQLAlchemy>=0.8.2
|
SQLAlchemy>=0.8.2
|
||||||
alembic>=0.4.1
|
alembic>=0.4.1
|
||||||
oslo.config>=1.4.0.0a3
|
oslo.config>=1.4.0.0a3
|
||||||
|
coverage>=4.3
|
||||||
|
xmltodict>=0.10.1 # MIT
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
hacking<0.11,>=0.10.0
|
hacking<0.11,>=0.10.0
|
||||||
|
|
||||||
coverage>=3.6
|
|
||||||
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
docutils>=0.11 # OSI-Approved Open Source, Public Domain
|
||||||
fixtures>=0.3.14
|
fixtures>=0.3.14
|
||||||
python-subunit>=0.0.18
|
python-subunit>=0.0.18
|
||||||
|
|
Loading…
Reference in New Issue