Beispiel #1
0
 def get(self, build: Build):
     """
     Return a build.
     """
     with nplusone.ignore("eager_load"):
         build.source = Source.query.options(joinedload("revision"),
                                             joinedload("patch")).get(
                                                 build.source_id)
     build.stats = list(ItemStat.query.filter(ItemStat.item_id == build.id))
     return self.respond_with_schema(build_schema, build)
Beispiel #2
0
 def get(self, build: Build):
     """
     Return a build.
     """
     with nplusone.ignore("eager_load"):
         build.revision = Revision.query.filter(
             Revision.sha == build.revision_sha,
             Revision.repository_id == build.repository_id,
         ).first()
     build.stats = list(ItemStat.query.filter(ItemStat.item_id == build.id))
     return self.respond_with_schema(build_schema, build)
Beispiel #3
0
    def post(self, repo: Repository):
        """
        Create a new build.
        """
        schema = BuildCreateSchema(strict=True, context={"repository": repo})
        result = self.schema_from_request(schema, partial=True)
        if result.errors:
            return self.respond(result.errors, 403)

        data = result.data

        # TODO(dcramer): only if we create a source via a patch will we need the author
        # author_data = data.pop('author')
        # if author_data.get('email'):
        #     author = Author.query.filter(
        #         Author.repository_id == repo.id, Author.email == author_data['email']
        #     ).first()
        # else:
        #     author = None
        # if not author:
        #     author = Author(repository_id=repo.id, **author_data)
        #     db.session.add(author)
        #     db.session.flush()

        # TODO(dcramer): need to handle patch case yet
        source = (Source.query.options(
            joinedload("author"), joinedload("revision")).filter(
                Source.revision_sha == data.pop("ref"),
                Source.repository_id == repo.id).first())

        build = Build(repository=repo, **data)
        # TODO(dcramer): we should convert source in the schema
        build.source = source
        # build.source_id = source.id
        build.author = source.author
        if not source.patch_id:
            if not build.label:
                build.label = source.revision.message.split("\n")[0]

        if not build.label:
            return self.error("missing build label")

        db.session.add(build)

        try:
            db.session.commit()
        except IntegrityError:
            db.session.rollback()
            return self.respond(status=422)

        result = build_schema.dump(build)
        assert not result.errors, "this should never happen"
        publish("builds", "build.create", result.data)
        return self.respond(result.data, 200)
Beispiel #4
0
    def post(self, repo: Repository):
        """
        Create a new build.
        """
        result = self.schema_from_request(build_create_schema, partial=True)
        if result.errors:
            return self.respond(result.errors, 403)
        data = result.data

        ref = data.pop('ref', None)
        if ref is None:
            return self.error('missing ref')

        try:
            revision = identify_revision(repo, ref)
        except UnknownRevision:
            current_app.logger.warn('invalid ref received', exc_info=True)
            return self.error('unable to find a revision matching ref')

        # TODO(dcramer): only if we create a source via a patch will we need the author
        # author_data = data.pop('author')
        # if author_data.get('email'):
        #     author = Author.query.filter(
        #         Author.repository_id == repo.id, Author.email == author_data['email']
        #     ).first()
        # else:
        #     author = None
        # if not author:
        #     author = Author(repository_id=repo.id, **author_data)
        #     db.session.add(author)
        #     db.session.flush()

        # TODO(dcramer): need to handle patch case yet
        source = Source.query.options(joinedload('author'), ).filter(
            Source.revision_sha == revision.sha,
            Source.repository_id == repo.id,
        ).first()

        build = Build(repository=repo, **data)
        # TODO(dcramer): we should convert source in the schema
        build.source = source
        # build.source_id = source.id
        build.author = source.author
        if not source.patch_id:
            if not build.label:
                build.label = source.revision.message.split('\n')[0]
        db.session.add(build)
        db.session.commit()

        return self.respond_with_schema(build_schema, build)
Beispiel #5
0
def merge_builds(target: Build, build: Build) -> Build:
    # explicitly unset the default id as target should begin as an empty instance
    target.id = None

    # Store the original build so we can retrieve its ID or number later, or
    # show a list of all builds in the UI
    target.original.append(build)

    # These properties should theoretically always be the same within a build
    # group, so merging is not necessary.  We assign here so the initial build
    # gets populated.
    target.source = build.source
    target.label = build.source.revision.message

    # Merge properties, if they already exist.  In the first run, everything
    # will be empty, since every group is initialized with an empty build.
    # Afterwards, we always use the more extreme value (e.g. earlier start
    # date or worse result).
    target.stats = target.stats + build.stats if target.stats else build.stats
    target.status = (
        Status(max(target.status.value, build.status.value))
        if target.status
        else build.status
    )
    target.result = (
        Result(max(target.result.value, build.result.value))
        if target.result
        else build.result
    )
    target.date_started = (
        min(target.date_started, build.date_started)
        if target.date_started and build.date_started
        else target.date_started or build.date_started
    )
    target.date_finished = (
        max(target.date_finished, build.date_finished)
        if target.date_finished and build.date_finished
        else target.date_finished or build.date_finished
    )
    target.provider = (
        "%s, %s" % (target.provider, build.provider)
        if target.provider
        else build.provider
    )

    # NOTE: The build number is not merged, as it would not convey any meaning
    # in the context of a build group.  In that fashion, build numbers should
    # not be used in the UI, until we create a better interface.

    # NOTE: We do not merge data here, as it is not really used in the UI
    # If there is an actual use for data, then it should be merged or appended

    return target
Beispiel #6
0
 def get(self, build: Build):
     """
     Return a build.
     """
     build.source = Source.query.options(joinedload('revision'),
                                         joinedload('patch')).get(
                                             build.source_id)
     return self.respond_with_schema(build_schema, build)
Beispiel #7
0
def merge_build_group(build_group):
    if len(build_group) == 1:
        build = build_group[0]
        build.original = [build]
        return build

    providers = groupby(build_group, lambda build: build.provider)
    latest_builds = [
        max(build, key=lambda build: build.number) for _, build in providers
    ]

    if len(latest_builds) == 1:
        build = latest_builds[0]
        build.original = [build]
        return build

    build = Build()
    build.original = []
    return reduce(merge_builds, latest_builds, build)
Beispiel #8
0
    def post(self, repo: Repository):
        """
        Create a new build.
        """
        result = self.schema_from_request(build_create_schema, partial=True)
        if result.errors:
            return self.respond(result.errors, 403)
        data = result.data

        revision_sha = data.pop('revision_sha')
        source = Source.query.filter(
            Source.revision_sha == revision_sha,
            Source.repository_id == repo.id,
        ).first()
        if not source:
            return self.error('invalid source')

        # TODO(dcramer): only if we create a source via a patch will we need the author
        # author_data = data.pop('author')
        # if author_data.get('email'):
        #     author = Author.query.filter(
        #         Author.repository_id == repo.id, Author.email == author_data['email']
        #     ).first()
        # else:
        #     author = None
        # if not author:
        #     author = Author(repository_id=repo.id, **author_data)
        #     db.session.add(author)
        #     db.session.flush()

        build = Build(repository=repo, **data)
        # TODO(dcramer): we should convert source in the schema
        build.source = source
        build.source_id = source.id
        if not source.patch_id:
            if not build.label:
                build.label = source.revision.message.split('\n')[0]
        assert build.source_id
        db.session.add(build)
        db.session.commit()

        return self.respond_with_schema(build_schema, build)
Beispiel #9
0
def merge_build_group(
    build_group: Tuple[Any, List[Build]], required_hook_ids: List[str] = None
) -> Build:
    # XXX(dcramer): required_hook_ids is still dirty here, but its our simplest way
    # to get it into place
    grouped_builds = groupby(
        build_group, lambda build: (str(build.hook_id), build.provider)
    )
    latest_builds = [
        max(build, key=lambda build: build.number) for _, build in grouped_builds
    ]

    build = Build()
    build.original = []
    if set(required_hook_ids or ()).difference(
        set(str(b.hook_id) for b in build_group)
    ):
        build.result = Result.failed

    return reduce(merge_builds, latest_builds, build)
Beispiel #10
0
    def setUp(self):
        zeus = Project.objects.create(
            name='zeus',
            url='https://github.com/lukaszb/zeus',
            repo_url='git://github.com/lukaszb/zeus.git',
        )
        self.buildset = Buildset.objects.create(
            project=zeus,
            number=1,
        )
        self.build1 = Build.objects.create(
            buildset=self.buildset,
            number=1,
        )
        self.build1_cmd1 = Command.objects.create(
            number=1,
            build=self.build1,
            title='Step 1 -- Configuration',
            cmd=['./configure'],
        )
        output = Output.objects.create(output='Configured')
        self.build1_cmd1.command_output = output
        delta = datetime.timedelta(seconds=2)
        self.build1_cmd1.started_at = self.build1_cmd1.created_at + delta
        self.build1_cmd1.finished_at = self.build1_cmd1.started_at + delta
        self.build1_cmd1.returncode = 0
        self.build1_cmd1.status = Status.PASSED
        self.build1_cmd1.save()

        self.build1_cmd2 = Command.objects.create(
            number=2,
            build=self.build1,
            title='Step 2 -- Build',
            cmd=['make', 'all'],
        )
        output = Output.objects.create(output='Build in progress ...')
        self.build1_cmd2.command_output = output
        self.build1_cmd2.started_at = self.build1_cmd2.created_at
        self.build1_cmd2.status = Status.RUNNING
        self.build1_cmd2.save()


        dt = datetime.datetime(2013, 7, 2, 22, 8)
        self.build2 = Build(
            buildset=self.buildset,
            number=2,
            created_at=dt,
            finished_at=(dt + datetime.timedelta(seconds=3)),
        )
        self.build2.save()
        cache.clear()
Beispiel #11
0
 def build_instance(self, data, **kwargs):
     revision = self.context.get("resolved_revision")
     build = Build(
         repository=self.context.get("repository"),
         revision_sha=revision.sha if revision else None,
         **data
     )
     if build.data is None:
         build.data = {}
     build.data["required_hook_ids"] = Hook.get_required_hook_ids(
         build.repository.id
     )
     if revision:
         if not build.label:
             build.label = revision.message.split("\n")[0]
         if not build.authors and revision.authors:
             build.authors = revision.authors
     return build
Beispiel #12
0
class TestBuildApi(BaseApiTestCase):
    maxDiff = None

    def setUp(self):
        zeus = Project.objects.create(
            name='zeus',
            url='https://github.com/lukaszb/zeus',
            repo_url='git://github.com/lukaszb/zeus.git',
        )
        self.buildset = Buildset.objects.create(
            project=zeus,
            number=1,
        )
        self.build1 = Build.objects.create(
            buildset=self.buildset,
            number=1,
        )
        self.build1_cmd1 = Command.objects.create(
            number=1,
            build=self.build1,
            title='Step 1 -- Configuration',
            cmd=['./configure'],
        )
        output = Output.objects.create(output='Configured')
        self.build1_cmd1.command_output = output
        delta = datetime.timedelta(seconds=2)
        self.build1_cmd1.started_at = self.build1_cmd1.created_at + delta
        self.build1_cmd1.finished_at = self.build1_cmd1.started_at + delta
        self.build1_cmd1.returncode = 0
        self.build1_cmd1.status = Status.PASSED
        self.build1_cmd1.save()

        self.build1_cmd2 = Command.objects.create(
            number=2,
            build=self.build1,
            title='Step 2 -- Build',
            cmd=['make', 'all'],
        )
        output = Output.objects.create(output='Build in progress ...')
        self.build1_cmd2.command_output = output
        self.build1_cmd2.started_at = self.build1_cmd2.created_at
        self.build1_cmd2.status = Status.RUNNING
        self.build1_cmd2.save()


        dt = datetime.datetime(2013, 7, 2, 22, 8)
        self.build2 = Build(
            buildset=self.buildset,
            number=2,
            created_at=dt,
            finished_at=(dt + datetime.timedelta(seconds=3)),
        )
        self.build2.save()
        cache.clear()

    def test_build_detail(self):
        url_params = {'name': 'zeus', 'buildset_no': 1, 'build_no': 1}
        url = reverse('zeus_api_build_detail', kwargs=url_params)
        response = self.client.get(url)
        expected = {
            'uri': self.make_api_build_detail_url('zeus', 1, 1),
            'url': self.build1.get_absolute_url(),
            'number': 1,
            'created_at': self.build1.created_at,
            'finished_at': self.build1.finished_at,
            'status': 'running',
            'commands': [
                {
                    'number': 1,
                    'title': 'Step 1 -- Configuration',
                    'cmd': './configure',
                    'output': 'Configured',
                    'started_at': self.build1_cmd1.started_at,
                    'finished_at': self.build1_cmd1.finished_at,
                    'status': 'passed',
                    'returncode': 0,
                },
                {
                    'number': 2,
                    'title': 'Step 2 -- Build',
                    'cmd': 'make all',
                    'output': 'Build in progress ...',
                    'started_at': self.build1_cmd2.started_at,
                    'finished_at': None,
                    'status': 'running',
                    'returncode': None,
                },
            ],
        }
        self.assertDictEqual(response.data, expected)

        url_params = {'name': 'zeus', 'buildset_no': 1, 'build_no': 2}
        url = reverse('zeus_api_build_detail', kwargs=url_params)
        response = self.client.get(url)
        expected = {
            'uri': self.make_api_build_detail_url('zeus', 1, 2),
            'url': self.build2.get_absolute_url(),
            'number': 2,
            'created_at': self.build2.created_at,
            'finished_at': self.build2.finished_at,
            'status': 'pending',
            'commands': [],
        }
        self.assertDictEqual(response.data, expected)

    def test_build_restart_fails_if_build_is_still_running(self):
        url_params = {'name': 'zeus', 'buildset_no': 1, 'build_no': 1}
        url = reverse('zeus_api_build_detail', kwargs=url_params)
        response = self.client.put(url)
        self.assertEqual(response.status_code, 409)

    def test_build_restart(self):
        url_params = {'name': 'zeus', 'buildset_no': 1, 'build_no': 1}
        url = reverse('zeus_api_build_detail', kwargs=url_params)
        self.build1.commands.update(status=Status.FAILED)

        response = self.client.put(url)
        self.assertEqual(response.status_code, 200)

        self.assertEqual(response.data['status'], Status.PENDING)
        self.assertIsNone(response.data['finished_at'])
        self.assertEqual(self.build1.commands.count(), 0)

    def test_build_status_is_changing_correctly(self):
        self.assertEqual(self.build1.status, Status.RUNNING)

        self.build1_cmd1.status = Status.FAILED
        self.build1_cmd1.save()
        self.assertEqual(self.build1.status, Status.FAILED)

        self.build1.commands.update(status=Status.PASSED)
        self.assertEqual(self.build1.status, Status.PASSED)

        self.build1.commands.all().delete()
        self.assertEqual(self.build1.status, Status.PENDING)

        # Create a command and make sure status us RUNNING once again
        Command.objects.create(
            number=1,
            build=self.build1,
            title='Step 1 -- Configuration',
            cmd=['./configure'],
            status=Status.RUNNING,
        )
        self.assertEqual(self.build1.status, Status.RUNNING)