This repository has been archived by the owner on Dec 15, 2018. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 58
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
artifact collection for bazel targets
Summary: This diff has Changes look at *.bazel.xml and appropriately create Targets for them. This involves a new model, BazelTarget, and a new artifact handler, BazelTargetHandler. Test Plan: unit tests Reviewers: anupc Reviewed By: anupc Subscribers: changesbot, kylec Differential Revision: https://tails.corp.dropbox.com/D224599
- Loading branch information
Naphat Sanguansin
committed
Aug 30, 2016
1 parent
9f69149
commit 92e94b9
Showing
9 changed files
with
249 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
import os | ||
|
||
from changes.artifacts.xunit import XunitHandler | ||
from changes.config import db | ||
from changes.constants import Status | ||
from changes.db.utils import get_or_create | ||
from changes.models.bazeltarget import BazelTarget | ||
from changes.models.test import TestCase | ||
from changes.models.testresult import TestResultManager | ||
from changes.storage.artifactstore import ARTIFACTSTORE_PREFIX | ||
from changes.utils.agg import aggregate_result | ||
|
||
|
||
class BazelTargetHandler(XunitHandler): | ||
FILENAMES = ('test.bazel.xml',) | ||
|
||
def process(self, fp, artifact): | ||
target_name = self._get_target_name(artifact) | ||
target, _ = get_or_create(BazelTarget, where={ | ||
'step_id': self.step.id, | ||
'job_id': self.step.job.id, | ||
'name': target_name, | ||
}) | ||
test_suites = self.get_test_suites(fp) | ||
tests = self.aggregate_tests_from_suites(test_suites) | ||
manager = TestResultManager(self.step, artifact) | ||
manager.save(tests) | ||
|
||
# add all tests to target | ||
for test in tests: | ||
test_case = TestCase.query.filter( | ||
TestCase.step == self.step, | ||
TestCase.name_sha == test.name_sha, | ||
).limit(1).first() | ||
target.tests.append(test_case) | ||
|
||
# update target metadata | ||
# TODO handle multiple files per target, i.e. sharding and running multiple times | ||
target.status = Status.finished | ||
target.result = aggregate_result([t.result for t in target.tests]) | ||
duration = 0 | ||
for t in test_suites: | ||
if t.duration is None: | ||
duration = None | ||
break | ||
duration += t.duration | ||
target.duration = duration | ||
target.date_created = min([t.date_created for t in test_suites]) | ||
db.session.add(target) | ||
db.session.commit() | ||
return tests | ||
|
||
def _get_target_name(self, artifact): | ||
"""Given an artifact, return the target name relative to the root | ||
of the repo. | ||
Essentially, we want to go from the artifact name of | ||
{artifact_store_prefix}foo/bar/baz/test.bazel.xml to | ||
//foo/bar:baz | ||
""" | ||
assert artifact.name.startswith(ARTIFACTSTORE_PREFIX) | ||
path = artifact.name[len(ARTIFACTSTORE_PREFIX):] | ||
dirname = os.path.dirname(path) | ||
dirname, target = os.path.split(dirname) | ||
target = dirname + ':' + target | ||
return '//' + target.lstrip('/') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
from __future__ import absolute_import, division | ||
|
||
import uuid | ||
|
||
from datetime import datetime | ||
from sqlalchemy import Column, DateTime, ForeignKey, Text, Integer | ||
from sqlalchemy.orm import backref, relationship | ||
|
||
from changes.config import db | ||
from changes.constants import Result, Status | ||
from changes.db.types.enum import Enum | ||
from changes.db.types.guid import GUID | ||
|
||
|
||
class BazelTarget(db.Model): | ||
__tablename__ = 'bazeltarget' | ||
id = Column(GUID, nullable=False, primary_key=True, default=uuid.uuid4) | ||
step_id = Column(GUID, ForeignKey('jobstep.id', ondelete="CASCADE"), nullable=False) | ||
job_id = Column(GUID, ForeignKey('job.id', ondelete="CASCADE"), nullable=False) | ||
name = Column(Text, nullable=False) | ||
status = Column(Enum(Status), nullable=False, default=Status.unknown) | ||
result = Column(Enum(Result), default=Result.unknown, nullable=False) | ||
duration = Column(Integer, default=0) | ||
date_created = Column(DateTime, default=datetime.utcnow, nullable=False) | ||
|
||
tests = relationship('TestCase', backref=backref('target')) | ||
|
||
def __init__(self, **kwargs): | ||
super(BazelTarget, self).__init__(**kwargs) | ||
if self.id is None: | ||
self.id = uuid.uuid4() | ||
if self.result is None: | ||
self.result = Result.unknown | ||
if self.status is None: | ||
self.status = Status.unknown | ||
if self.date_created is None: | ||
self.date_created = datetime.utcnow() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import mock | ||
import pytest | ||
|
||
from cStringIO import StringIO | ||
|
||
from changes.artifacts.bazel_target import BazelTargetHandler | ||
from changes.constants import Result, Status | ||
from changes.models.bazeltarget import BazelTarget | ||
from changes.testutils import ( | ||
SAMPLE_XUNIT, SAMPLE_XUNIT_MULTIPLE_SUITES, SAMPLE_XUNIT_MULTIPLE_SUITES_COMPLETE_TIME | ||
) | ||
from changes.testutils.cases import TestCase | ||
|
||
|
||
@pytest.mark.parametrize('artifact_name, target_name', [ | ||
('artifactstore/some/path/here/here_test/test.bazel.xml', | ||
'//some/path/here:here_test'), | ||
('artifactstore/some/path/here/target/test.bazel.xml', | ||
'//some/path/here:target'), | ||
('artifactstore/here_test/test.bazel.xml', '//:here_test'), | ||
]) | ||
def test_get_target_name(artifact_name, target_name): | ||
artifact = mock.MagicMock() | ||
artifact.name = artifact_name | ||
handler = BazelTargetHandler(None) | ||
assert handler._get_target_name(artifact) == target_name | ||
|
||
|
||
class BazelTargetTestCase(TestCase): | ||
|
||
def test_single(self): | ||
project = self.create_project() | ||
build = self.create_build(project) | ||
job = self.create_job(build) | ||
jobphase = self.create_jobphase(job) | ||
jobstep = self.create_jobstep(jobphase) | ||
artifact = self.create_artifact( | ||
jobstep, 'artifactstore/some/target/target_test/test.bazel.xml') | ||
|
||
handler = BazelTargetHandler(jobstep) | ||
|
||
fp = StringIO(SAMPLE_XUNIT) | ||
tests = handler.process(fp, artifact) | ||
|
||
target = BazelTarget.query.filter( | ||
BazelTarget.name == '//some/target:target_test', BazelTarget.step == jobstep).limit(1).first() | ||
assert target.job == job | ||
assert target.status is Status.finished | ||
assert target.result is Result.failed | ||
assert target.duration == 77 | ||
shas = [t.name_sha for t in target.tests] | ||
for test_result in tests: | ||
assert test_result.name_sha in shas | ||
assert len(tests) == len(target.tests) | ||
|
||
def test_multiple_duration_none(self): | ||
project = self.create_project() | ||
build = self.create_build(project) | ||
job = self.create_job(build) | ||
jobphase = self.create_jobphase(job) | ||
jobstep = self.create_jobstep(jobphase) | ||
artifact = self.create_artifact( | ||
jobstep, 'artifactstore/some/target/target_test/test.bazel.xml') | ||
|
||
handler = BazelTargetHandler(jobstep) | ||
|
||
fp = StringIO(SAMPLE_XUNIT_MULTIPLE_SUITES) | ||
tests = handler.process(fp, artifact) | ||
|
||
target = BazelTarget.query.filter( | ||
BazelTarget.name == '//some/target:target_test', BazelTarget.step == jobstep).limit(1).first() | ||
assert target.job == job | ||
assert target.status is Status.finished | ||
assert target.result is Result.failed | ||
assert target.duration is None | ||
shas = [t.name_sha for t in target.tests] | ||
for test_result in tests: | ||
assert test_result.name_sha in shas | ||
assert len(tests) == len(target.tests) | ||
|
||
def test_multiple_duration_complete(self): | ||
project = self.create_project() | ||
build = self.create_build(project) | ||
job = self.create_job(build) | ||
jobphase = self.create_jobphase(job) | ||
jobstep = self.create_jobstep(jobphase) | ||
artifact = self.create_artifact( | ||
jobstep, 'artifactstore/some/target/target_test/test.bazel.xml') | ||
|
||
handler = BazelTargetHandler(jobstep) | ||
|
||
fp = StringIO(SAMPLE_XUNIT_MULTIPLE_SUITES_COMPLETE_TIME) | ||
tests = handler.process(fp, artifact) | ||
|
||
target = BazelTarget.query.filter( | ||
BazelTarget.name == '//some/target:target_test', BazelTarget.step == jobstep).limit(1).first() | ||
assert target.job == job | ||
assert target.status is Status.finished | ||
assert target.result is Result.passed | ||
assert target.duration == 1577 | ||
shas = [t.name_sha for t in target.tests] | ||
for test_result in tests: | ||
assert test_result.name_sha in shas | ||
assert len(tests) == len(target.tests) |