예제 #1
0
    def submit(self, job):
        """Submit the job to treeherder.

        :param job: Treeherder job instance to use for submission.

        """
        job.add_submit_timestamp(int(time.time()))

        # We can only submit job info once, so it has to be done in completed
        if self._job_details:
            job.add_artifact('Job Info', 'json', {'job_details': self._job_details})

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        logger.info('Sending results to Treeherder: {}'.format(job_collection.to_json()))
        url = urlparse(self.url)
        client = TreeherderClient(protocol=url.scheme, host=url.hostname,
                                  client_id=self.client_id, secret=self.secret)
        client.post_collection(self.repository, job_collection)

        logger.info('Results are available to view at: {}'.format(
                    urljoin(self.url,
                            JOB_FRAGMENT.format(repository=self.repository,
                                                revision=self.revision))))
예제 #2
0
    def submit(self, job):
        """Submit the job to treeherder.

        :param job: Treeherder job instance to use for submission.

        """
        job.add_submit_timestamp(int(time.time()))

        if self._job_details:
            job.add_artifact('Job Info', 'json',
                             {'job_details': copy.deepcopy(self._job_details)})
            self._job_details = []

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        logger.info('Sending results to Treeherder: {}'.format(
            job_collection.to_json()))
        self.client.post_collection(self.repository, job_collection)

        logger.info('Results are available to view at: {}'.format(
            urljoin(
                self.client.server_url,
                JOB_FRAGMENT.format(repository=self.repository,
                                    revision=self.revision))))
    def submit(self, job, logs=None):
        logs = logs or []

        # We can only submit job info once, so it has to be done in completed
        if self._job_details:
            job.add_artifact('Job Info', 'json',
                             {'job_details': self._job_details})

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        print('Sending results to Treeherder: {}'.format(
            job_collection.to_json()))
        url = urlparse(self.url)

        client = TreeherderClient(protocol=url.scheme,
                                  host=url.hostname,
                                  client_id=self.client_id,
                                  secret=self.secret)
        client.post_collection(self.repository, job_collection)

        print('Results are available to view at: {}'.format(
            urljoin(
                self.url,
                JOB_FRAGMENT.format(repository=self.repository,
                                    revision=self.revision))))
예제 #4
0
    def submit_running(self, machine, build_url, project, revision_hash, tests=None):
        """Submit tests running notifications to Treeherder

        :param machine: machine id
        :param build_url: url to build being tested.
        :param project: repository of build.
        :param revision_hash: Treeherder revision hash of build.
        :param tests: Lists of tests to be reported.
        """
        if tests is None:
            tests = []
        logger.debug('AutophoneTreeherder.submit_running: %s' % tests)
        if not self.url or not revision_hash:
            logger.debug('AutophoneTreeherder.submit_running: no url/revision hash')
            return

        tjc = TreeherderJobCollection()

        for t in tests:
            logger.debug('AutophoneTreeherder.submit_running: '
                         'for %s %s' % (t.name, project))

            t.submit_timestamp = timestamp_now()
            t.start_timestamp = timestamp_now()

            tj = tjc.get_job()
            tj.add_tier(self.options.treeherder_tier)
            tj.add_revision_hash(revision_hash)
            tj.add_project(project)
            tj.add_job_guid(t.job_guid)
            tj.add_job_name(t.job_name)
            tj.add_job_symbol(t.job_symbol)
            tj.add_group_name(t.group_name)
            tj.add_group_symbol(t.group_symbol)
            tj.add_product_name('fennec')
            tj.add_state(TestState.RUNNING)
            tj.add_submit_timestamp(t.submit_timestamp)
            tj.add_start_timestamp(t.start_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_end_timestamp(0)
            #
            tj.add_machine(machine)
            tj.add_build_info('android', t.phone.platform, t.phone.architecture)
            tj.add_machine_info('android',t.phone.platform, t.phone.architecture)
            tj.add_option_collection({'opt': True})

            tj.add_artifact('buildapi', 'json', {
                'buildername': t.get_buildername(project)})
            tj.add_artifact('privatebuild', 'json', {
                'build_url': build_url,
                'config_file': t.config_file,
                'chunk': t.chunk})
            tjc.add(tj)

        logger.debug('AutophoneTreeherder.submit_running: tjc: %s' %
                     tjc.to_json())

        self.queue_request(machine, project, tjc)
예제 #5
0
    def submit_pending(self, tests=[]):
        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_pending: %s' % tests)
        if not self.url or not self.worker.build.revision_hash:
            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_pending: no url/revision hash')
            return

        tjc = TreeherderJobCollection(job_type='update')

        if not tests:
            tests = self.worker.runnable_tests

        for t in tests:
            t.message = None
            t.submit_timestamp = timestamp_now()
            t.job_guid = generate_guid()
            t.job_details = []

            self.worker.loggerdeco.info('creating Treeherder job %s for %s %s, '
                                        'revision: %s, revision_hash: %s' % (
                                            t.job_guid, t.name, t.build.tree,
                                            t.build.revision, t.build.revision_hash))

            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_pending: '
                                         'test config_file=%s, config sections=%s' % (
                                             t.config_file, t.cfg.sections()))

            tj = tjc.get_job()
            tj.add_revision_hash(self.worker.build.revision_hash)
            tj.add_project(self.worker.build.tree)
            tj.add_job_guid(t.job_guid)
            tj.add_job_name(t.job_name)
            tj.add_job_symbol(t.job_symbol)
            tj.add_group_name(t.group_name)
            tj.add_group_symbol(t.group_symbol)
            tj.add_product_name('fennec')
            tj.add_state(TestState.PENDING)
            tj.add_submit_timestamp(t.submit_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_start_timestamp(t.submit_timestamp)
            tj.add_end_timestamp(t.submit_timestamp)
            #
            tj.add_machine(t.phone.id)
            tj.add_build_url(self.worker.build.url)
            tj.add_build_info('android', t.phone.platform, t.phone.architecture)
            tj.add_machine_info('android',t.phone.platform, t.phone.architecture)
            tj.add_option_collection({'opt': True})

            # Fake the buildername from buildbot...
            tj.add_artifact('buildapi', 'json', {'buildername': t.buildername})

            tjc.add(tj)

        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_pending: tjc: %s' % (
            tjc.to_json()))

        self.post_request(tjc)
    def submit_pending(self,
                       machine,
                       build_url,
                       project,
                       revision,
                       build_type,
                       build_abi,
                       build_platform,
                       build_sdk,
                       builder_type,
                       tests=[]):
        """Submit tests pending notifications to Treeherder

        :param machine: machine id
        :param build_url: url to build being tested.
        :param project: repository of build.
        :param revision: Either a URL to the changeset or the revision id.
        :param tests: Lists of tests to be reported.
        """
        logger = utils.getLogger()
        logger.debug('AutophoneTreeherder.submit_pending: %s', tests)
        if not self.url or not revision:
            logger.debug('AutophoneTreeherder.submit_pending: no url/revision')
            return

        tjc = TreeherderJobCollection()

        for t in tests:
            logger.debug('AutophoneTreeherder.submit_pending: for %s %s',
                         t.name, project)

            t.message = None
            t.submit_timestamp = timestamp_now()
            t.job_details = []

            tj = self._create_job(tjc, machine, build_url, project, revision,
                                  build_type, build_abi, build_platform,
                                  build_sdk, builder_type, t)
            tj.add_state(TestState.PENDING)
            tj.add_submit_timestamp(t.submit_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_start_timestamp(0)
            tj.add_end_timestamp(0)
            tjc.add(tj)

        logger.debug('AutophoneTreeherder.submit_pending: tjc: %s',
                     tjc.to_json())

        self.queue_request(machine, project, tjc)
예제 #7
0
    def submit_running(self, tests=[]):
        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_running: %s' % tests)
        if not self.url or not self.worker.build.revision_hash:
            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_running: no url/revision hash')
            return

        tjc = TreeherderJobCollection(job_type='update')

        if not tests:
            tests = self.worker.runnable_tests

        for t in tests:
            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_running: '
                                         'for %s %s' % (t.name, t.build.tree))

            t.start_timestamp = timestamp_now()

            tj = tjc.get_job()
            tj.add_revision_hash(self.worker.build.revision_hash)
            tj.add_project(self.worker.build.tree)
            tj.add_job_guid(t.job_guid)
            tj.add_job_name(t.job_name)
            tj.add_job_symbol(t.job_symbol)
            tj.add_group_name(t.group_name)
            tj.add_group_symbol(t.group_symbol)
            tj.add_product_name('fennec')
            tj.add_state(TestState.RUNNING)
            tj.add_submit_timestamp(t.submit_timestamp)
            tj.add_start_timestamp(t.start_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_end_timestamp(t.start_timestamp)
            #
            tj.add_machine(t.phone.id)
            tj.add_build_url(self.worker.build.url)
            tj.add_build_info('android', t.phone.platform, t.phone.architecture)
            tj.add_machine_info('android',t.phone.platform, t.phone.architecture)
            tj.add_option_collection({'opt': True})

            tj.add_artifact('buildapi', 'json', {'buildername': t.buildername})
            tjc.add(tj)

        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_running: tjc: %s' %
                                     tjc.to_json())

        self.post_request(tjc)
    def submit(self, job, logs=None):
        logs = logs or []

        # We can only submit job info once, so it has to be done in completed
        if self._job_details:
            job.add_artifact('Job Info', 'json', {'job_details': self._job_details})

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        print('Sending results to Treeherder: {}'.format(job_collection.to_json()))
        url = urlparse(self.url)
       
        client = TreeherderClient(protocol=url.scheme, host=url.hostname,
                                  client_id=self.client_id, secret=self.secret)
        client.post_collection(self.repository, job_collection)

        print('Results are available to view at: {}'.format(
            urljoin(self.url,
                    JOB_FRAGMENT.format(repository=self.repository, revision=self.revision))))
예제 #9
0
    def submit_results(self, job):
        job.add_project(self.project)
        job.add_revision_hash(self.retrieve_revision_hash())
        job.add_submit_timestamp(int(time.time()))

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        # self.logger.info
        print('Sending results to Treeherder: %s' % job_collection.to_json())

        url = urlparse(self.url)
        client = TreeherderClient(protocol=url.scheme, host=url.hostname,
                                  client_id=self.client_id, secret=self.secret)
        client.post_collection(self.project, job_collection)

        # self.logger.info
        print('Results are available to view at: %s' % (
            urljoin(self.url,
                    REVISON_FRAGMENT % (self.project, self.revision))))
    def test_send_job_collection(self, mock_send):
        """Can add a treeherder collections to a TreeherderRequest."""

        tjc = TreeherderJobCollection()

        for job in self.job_data:

            tjc.add(tjc.get_job(job))

        req = TreeherderRequest(
            protocol='http',
            host='host',
            project='project',
            oauth_key='key',
            oauth_secret='secret',
        )

        req.post(tjc)

        self.assertEqual(mock_send.call_count, 1)
        self.assertEqual(tjc.to_json(), mock_send.call_args_list[0][1]['data'])
예제 #11
0
    def submit_running(self, machine, build_url, project, revision, build_type,
                       build_abi, build_platform, build_sdk, builder_type, tests=[]):
        """Submit tests running notifications to Treeherder

        :param machine: machine id
        :param build_url: url to build being tested.
        :param project: repository of build.
        :param revision: Either a URL to the changeset or the revision id.
        :param tests: Lists of tests to be reported.
        """
        logger = utils.getLogger()
        logger.debug('AutophoneTreeherder.submit_running: %s', tests)
        if not self.url or not revision:
            logger.debug('AutophoneTreeherder.submit_running: no url/revision')
            return

        tjc = TreeherderJobCollection()

        for t in tests:
            logger.debug('AutophoneTreeherder.submit_running: for %s %s', t.name, project)

            t.submit_timestamp = timestamp_now()
            t.start_timestamp = timestamp_now()

            tj = self._create_job(tjc, machine, build_url, project, revision,
                                  build_type, build_abi, build_platform,
                                  build_sdk, builder_type, t)
            tj.add_state(TestState.RUNNING)
            tj.add_submit_timestamp(t.submit_timestamp)
            tj.add_start_timestamp(t.start_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_end_timestamp(0)
            tjc.add(tj)

        logger.debug('AutophoneTreeherder.submit_running: tjc: %s',
                     tjc.to_json())

        self.queue_request(machine, project, tjc)
예제 #12
0
    def submit(self, job):
        """Submit the job to treeherder.

        :param job: Treeherder job instance to use for submission.

        """
        job.add_submit_timestamp(int(time.time()))

        if self._job_details:
            job.add_artifact('Job Info', 'json',
                             {'job_details': copy.deepcopy(self._job_details)})
            self._job_details = []

        job_collection = TreeherderJobCollection()
        job_collection.add(job)

        logger.info('Sending results to Treeherder: {}'.format(job_collection.to_json()))
        self.client.post_collection(self.repository, job_collection)

        logger.info('Results are available to view at: {}'.format(
                    urljoin('{0}://{1}'.format(self.client.protocol, self.client.host),
                            JOB_FRAGMENT.format(repository=self.repository,
                                                revision=self.revision))))
    def test_send_job_collection(self, mock_send):
        """Can add a treeherder collections to a TreeherderRequest."""

        tjc = TreeherderJobCollection()

        for job in self.job_data:

            tjc.add( tjc.get_job(job) )

        req = TreeherderRequest(
            protocol='http',
            host='host',
            project='project',
            oauth_key='key',
            oauth_secret='secret',
            )

        req.post(tjc)

        self.assertEqual(mock_send.call_count, 1)
        self.assertEqual(
            tjc.to_json(),
            mock_send.call_args_list[0][1]['data']
            )
예제 #14
0
파일: treeherder.py 프로젝트: 4gh/gaia
    def post_to_treeherder(self, tests):
        version = mozversion.get_version(
            binary=self.bin, sources=self.sources,
            dm_type='adb', device_serial=self.device_serial)

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get('device_id')
        device_firmware_version_release = \
            version.get('device_firmware_version_release')

        if not device:
            self.logger.error('Submitting to Treeherder is currently limited '
                              'to devices.')
            return

        try:
            group = DEVICE_GROUP_MAP[device][device_firmware_version_release]
            job.add_group_name(group['name'])
            job.add_group_symbol(group['symbol'])
            job.add_job_name('Gaia Python Integration Test (%s)' % group['symbol'])
            job.add_job_symbol('Gip')
        except KeyError:
            self.logger.error('Unknown device id: %s or device firmware '
                              'version: %s. Unable to determine Treeherder '
                              'group. Supported devices: %s'
                              % (device, device_firmware_version_release,
                                 ['%s: %s' % (k, [fw for fw in v.keys()])
                                  for k, v in DEVICE_GROUP_MAP.iteritems()]))
            return

        # Determine revision hash from application revision
        revision = version['application_changeset']
        project = version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            self.treeherder_url,
            'api/project/%s/revision-lookup/?revision=%s' % (
                project, revision))
        self.logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result('testfailed')
        else:
            job.add_result('success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        # All B2G device builds are currently opt builds
        job.add_option_collection({'opt': True})

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type': 'link',
            'title': 'Gaia revision:',
            'url': 'https://github.com/mozilla-b2g/gaia/commit/%s' %
                   version.get('gaia_changeset'),
            'value': version.get('gaia_changeset'),
        }, {
            'content_type': 'text',
            'title': 'Gaia date:',
            'value': version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': version.get('device_id')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (date):',
            'value': version.get('device_firmware_date') and time.strftime(
                date_format, time.localtime(int(
                    version.get('device_firmware_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device firmware (incremental):',
            'value': version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': version.get('device_firmware_version_release')
        }]

        ci_url = os.environ.get('BUILD_URL')
        if ci_url:
            job_details.append({
                'url': ci_url,
                'value': ci_url,
                'content_type': 'link',
                'title': 'CI build:'})

        # Attach logcat
        adb_device = ADBDevice(self.device_serial)
        with tempfile.NamedTemporaryFile(suffix='logcat.txt') as f:
            f.writelines(adb_device.get_logcat())
            self.logger.debug('Logcat stored in: %s' % f.name)
            try:
                url = self.upload_to_s3(f.name)
                job_details.append({
                    'url': url,
                    'value': 'logcat.txt',
                    'content_type': 'link',
                    'title': 'Log:'})
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload logcat.txt',
                    'content_type': 'text',
                    'title': 'Error:'})

        # Attach log files
        handlers = [handler for handler in self.logger.handlers
                    if isinstance(handler, StreamHandler) and
                    os.path.exists(handler.stream.name)]
        for handler in handlers:
            path = handler.stream.name
            filename = os.path.split(path)[-1]
            try:
                url = self.upload_to_s3(path)
                job_details.append({
                    'url': url,
                    'value': filename,
                    'content_type': 'link',
                    'title': 'Log:'})
                # Add log reference
                if type(handler.formatter) is TbplFormatter or \
                        type(handler.formatter) is LogLevelFilter and \
                        type(handler.formatter.inner) is TbplFormatter:
                    job.add_log_reference(filename, url)
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload %s' % filename,
                    'content_type': 'text',
                    'title': 'Error:'})

        # Attach reports
        for report in [self.html_output, self.xml_output]:
            if report is not None:
                filename = os.path.split(report)[-1]
                try:
                    url = self.upload_to_s3(report)
                    job_details.append({
                        'url': url,
                        'value': filename,
                        'content_type': 'link',
                        'title': 'Report:'})
                except S3UploadError:
                    job_details.append({
                        'value': 'Failed to upload %s' % filename,
                        'content_type': 'text',
                        'title': 'Error:'})

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=os.environ.get('TREEHERDER_KEY'),
            oauth_secret=os.environ.get('TREEHERDER_SECRET'))
        self.logger.debug('Sending results to Treeherder: %s' %
                          job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug('Response: %s' % response.read())
        assert response.status == 200, 'Failed to send results!'
        self.logger.info('Results are available to view at: %s' % (
            urljoin(self.treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' % (
                project, revision))))
예제 #15
0
    def post_to_treeherder(self, tests):
        version = mozversion.get_version(binary=self.bin,
                                         sources=self.sources,
                                         dm_type='adb',
                                         device_serial=self.device_serial)

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get('device_id')
        device_firmware_version_release = \
            version.get('device_firmware_version_release')

        if not device:
            self.logger.error('Submitting to Treeherder is currently limited '
                              'to devices.')
            return

        try:
            group = DEVICE_GROUP_MAP[device][device_firmware_version_release]
            job.add_group_name(group['name'])
            job.add_group_symbol(group['symbol'])
            job.add_job_name('Gaia Python Integration Test (%s)' %
                             group['symbol'])
            job.add_job_symbol('Gip')
        except KeyError:
            self.logger.error('Unknown device id: %s or device firmware '
                              'version: %s. Unable to determine Treeherder '
                              'group. Supported devices: %s' %
                              (device, device_firmware_version_release, [
                                  '%s: %s' % (k, [fw for fw in v.keys()])
                                  for k, v in DEVICE_GROUP_MAP.iteritems()
                              ]))
            return

        # Determine revision hash from application revision
        revision = version['application_changeset']
        project = version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            self.treeherder_url,
            'api/project/%s/revision-lookup/?revision=%s' %
            (project, revision))
        self.logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result('testfailed')
        else:
            job.add_result('success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        # All B2G device builds are currently opt builds
        job.add_option_collection({'opt': True})

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type':
            'link',
            'title':
            'Gaia revision:',
            'url':
            'https://github.com/mozilla-b2g/gaia/commit/%s' %
            version.get('gaia_changeset'),
            'value':
            version.get('gaia_changeset'),
        }, {
            'content_type':
            'text',
            'title':
            'Gaia date:',
            'value':
            version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': version.get('device_id')
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (date):',
            'value':
            version.get('device_firmware_date') and time.strftime(
                date_format,
                time.localtime(int(version.get('device_firmware_date')))),
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (incremental):',
            'value':
            version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': version.get('device_firmware_version_release')
        }]

        ci_url = os.environ.get('BUILD_URL')
        if ci_url:
            job_details.append({
                'url': ci_url,
                'value': ci_url,
                'content_type': 'link',
                'title': 'CI build:'
            })

        # Attach logcat
        adb_device = ADBDevice(self.device_serial)
        with tempfile.NamedTemporaryFile(suffix='logcat.txt') as f:
            f.writelines(adb_device.get_logcat())
            self.logger.debug('Logcat stored in: %s' % f.name)
            try:
                url = self.upload_to_s3(f.name)
                job_details.append({
                    'url': url,
                    'value': 'logcat.txt',
                    'content_type': 'link',
                    'title': 'Log:'
                })
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload logcat.txt',
                    'content_type': 'text',
                    'title': 'Error:'
                })

        # Attach log files
        handlers = [
            handler for handler in self.logger.handlers
            if isinstance(handler, StreamHandler)
            and os.path.exists(handler.stream.name)
        ]
        for handler in handlers:
            path = handler.stream.name
            filename = os.path.split(path)[-1]
            try:
                url = self.upload_to_s3(path)
                job_details.append({
                    'url': url,
                    'value': filename,
                    'content_type': 'link',
                    'title': 'Log:'
                })
                # Add log reference
                if type(handler.formatter) is TbplFormatter or \
                        type(handler.formatter) is LogLevelFilter and \
                        type(handler.formatter.inner) is TbplFormatter:
                    job.add_log_reference(filename, url)
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload %s' % filename,
                    'content_type': 'text',
                    'title': 'Error:'
                })

        # Attach reports
        for report in [self.html_output, self.xml_output]:
            if report is not None:
                filename = os.path.split(report)[-1]
                try:
                    url = self.upload_to_s3(report)
                    job_details.append({
                        'url': url,
                        'value': filename,
                        'content_type': 'link',
                        'title': 'Report:'
                    })
                except S3UploadError:
                    job_details.append({
                        'value': 'Failed to upload %s' % filename,
                        'content_type': 'text',
                        'title': 'Error:'
                    })

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=os.environ.get('TREEHERDER_KEY'),
            oauth_secret=os.environ.get('TREEHERDER_SECRET'))
        self.logger.debug('Sending results to Treeherder: %s' %
                          job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug('Response: %s' % response.read())
        assert response.status == 200, 'Failed to send results!'
        self.logger.info(
            'Results are available to view at: %s' %
            (urljoin(self.treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' %
                     (project, revision))))
예제 #16
0
파일: treeherder.py 프로젝트: Aswinn/gaia
    def post_to_treeherder(self, tests):
        self.logger.info('\nTREEHERDER\n----------')
        version = mozversion.get_version(
            binary=self.bin, sources=self.sources,
            dm_type='adb', device_serial=self.device_serial)

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get('device_id')
        if not device:
            self.logger.error('Submitting to Treeherder is currently limited '
                              'to devices.')
            return

        try:
            group = DEVICE_GROUP_MAP[device]
            job.add_group_name(group['name'])
            job.add_group_symbol(group['symbol'])
            job.add_job_name('Gaia Python Integration Test (%s)' % device)
            job.add_job_symbol('Gip')
        except KeyError:
            self.logger.error('Unknown device id: %s, unable to determine '
                              'Treeherder group. Supported device ids: %s' % (
                                  device, DEVICE_GROUP_MAP.keys()))
            return

        # Determine revision hash from application revision
        revision = version['application_changeset']
        project = version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            self.treeherder_url,
            'api/project/%s/revision-lookup/?revision=%s' % (
                project, revision))
        self.logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result('testfailed')
        else:
            job.add_result('success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        # All B2G device builds are currently opt builds
        job.add_option_collection({'opt': True})

        # TODO: Add log reference
        # job.add_log_reference()

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type': 'link',
            'title': 'Gaia revision:',
            'url': 'https://github.com/mozilla-b2g/gaia/commit/%s' %
                   version.get('gaia_changeset'),
            'value': version.get('gaia_changeset'),
        }, {
            'content_type': 'text',
            'title': 'Gaia date:',
            'value': version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': version.get('device_id')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (date):',
            'value': version.get('device_firmware_date') and time.strftime(
                date_format, time.localtime(int(
                    version.get('device_firmware_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device firmware (incremental):',
            'value': version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': version.get('device_firmware_version_release')
        }]

        if self.ci_url:
            job_details.append({
                'url': self.ci_url,
                'value': self.ci_url,
                'content_type': 'link',
                'title': 'CI build:'})

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        # TODO: Add XML/HTML reports as artifacts
        # job.add_artifact()

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=self.treeherder_key,
            oauth_secret=self.treeherder_secret)
        self.logger.debug('Sending results to Treeherder: %s' %
                          job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug('Response: %s' % response.read())
        assert response.status == 200, 'Failed to send results!'
        self.logger.info('Results are available to view at: %s' % (
            urljoin(self.treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' % (
                project, revision))))
예제 #17
0
파일: treeherder.py 프로젝트: kirchner/gaia
    def post_to_treeherder(self, tests):
        version = mozversion.get_version(
            binary=self.bin, sources=self.sources, dm_type="adb", device_serial=self.device_serial
        )

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get("device_id")
        device_firmware_version_release = version.get("device_firmware_version_release")

        if not device:
            self.logger.error("Submitting to Treeherder is currently limited " "to devices.")
            return

        try:
            group = DEVICE_GROUP_MAP[device][device_firmware_version_release]
            job.add_group_name(group["name"])
            job.add_group_symbol(group["symbol"])
            job.add_job_name("Gaia Python Integration Test (%s)" % group["symbol"])
            job.add_job_symbol("Gip")
        except KeyError:
            self.logger.error(
                "Unknown device id: %s or device firmware "
                "version: %s. Unable to determine Treeherder "
                "group. Supported devices: %s"
                % (
                    device,
                    device_firmware_version_release,
                    ["%s: %s" % (k, [fw for fw in v.keys()]) for k, v in DEVICE_GROUP_MAP.iteritems()],
                )
            )
            return

        # Determine revision hash from application revision
        revision = version["application_changeset"]
        project = version["application_repository"].split("/")[-1]
        lookup_url = urljoin(self.treeherder_url, "api/project/%s/revision-lookup/?revision=%s" % (project, revision))
        self.logger.debug("Getting revision hash from: %s" % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), (
            "Unable to determine revision hash for %s. " "Perhaps it has not been ingested by " "Treeherder?" % revision
        )
        revision_hash = response.json()[revision]["revision_hash"]
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name("b2g")
        job.add_state("completed")

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result("testfailed")
        else:
            job.add_result("success")

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info("b2g", "b2g-device-image", "x86")
        job.add_machine_info("b2g", "b2g-device-image", "x86")

        # All B2G device builds are currently opt builds
        job.add_option_collection({"opt": True})

        date_format = "%d %b %Y %H:%M:%S"
        job_details = [
            {
                "content_type": "link",
                "title": "Gaia revision:",
                "url": "https://github.com/mozilla-b2g/gaia/commit/%s" % version.get("gaia_changeset"),
                "value": version.get("gaia_changeset"),
            },
            {
                "content_type": "text",
                "title": "Gaia date:",
                "value": version.get("gaia_date")
                and time.strftime(date_format, time.localtime(int(version.get("gaia_date")))),
            },
            {"content_type": "text", "title": "Device identifier:", "value": version.get("device_id")},
            {
                "content_type": "text",
                "title": "Device firmware (date):",
                "value": version.get("device_firmware_date")
                and time.strftime(date_format, time.localtime(int(version.get("device_firmware_date")))),
            },
            {
                "content_type": "text",
                "title": "Device firmware (incremental):",
                "value": version.get("device_firmware_version_incremental"),
            },
            {
                "content_type": "text",
                "title": "Device firmware (release):",
                "value": version.get("device_firmware_version_release"),
            },
        ]

        ci_url = os.environ.get("BUILD_URL")
        if ci_url:
            job_details.append({"url": ci_url, "value": ci_url, "content_type": "link", "title": "CI build:"})

        # Attach logcat
        adb_device = ADBDevice(self.device_serial)
        with tempfile.NamedTemporaryFile(suffix="logcat.txt") as f:
            f.writelines(adb_device.get_logcat())
            self.logger.debug("Logcat stored in: %s" % f.name)
            try:
                url = self.upload_to_s3(f.name)
                job_details.append({"url": url, "value": "logcat.txt", "content_type": "link", "title": "Log:"})
            except S3UploadError:
                job_details.append({"value": "Failed to upload logcat.txt", "content_type": "text", "title": "Error:"})

        # Attach log files
        handlers = [
            handler
            for handler in self.logger.handlers
            if isinstance(handler, StreamHandler) and os.path.exists(handler.stream.name)
        ]
        for handler in handlers:
            path = handler.stream.name
            filename = os.path.split(path)[-1]
            try:
                url = self.upload_to_s3(path)
                job_details.append({"url": url, "value": filename, "content_type": "link", "title": "Log:"})
                # Add log reference
                if (
                    type(handler.formatter) is TbplFormatter
                    or type(handler.formatter) is LogLevelFilter
                    and type(handler.formatter.inner) is TbplFormatter
                ):
                    job.add_log_reference(filename, url)
            except S3UploadError:
                job_details.append(
                    {"value": "Failed to upload %s" % filename, "content_type": "text", "title": "Error:"}
                )

        # Attach reports
        for report in [self.html_output]:
            if report is not None:
                filename = os.path.split(report)[-1]
                try:
                    url = self.upload_to_s3(report)
                    job_details.append({"url": url, "value": filename, "content_type": "link", "title": "Report:"})
                except S3UploadError:
                    job_details.append(
                        {"value": "Failed to upload %s" % filename, "content_type": "text", "title": "Error:"}
                    )

        if job_details:
            job.add_artifact("Job Info", "json", {"job_details": job_details})

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=os.environ.get("TREEHERDER_KEY"),
            oauth_secret=os.environ.get("TREEHERDER_SECRET"),
        )
        self.logger.debug("Sending results to Treeherder: %s" % job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug("Response: %s" % response.read())
        assert response.status == 200, "Failed to send results!"
        self.logger.info(
            "Results are available to view at: %s"
            % (urljoin(self.treeherder_url, "/ui/#/jobs?repo=%s&revision=%s" % (project, revision)))
        )
예제 #18
0
def main():
    submit_time, start_time, end_time = argv[1:4]

    config = get_config()

    app_revision, app_repository = get_app_information(config)
    files = get_files(config)
    build_version = get_build_version(os.path.basename(files[0]))
    push_time = int(os.stat(files[0]).st_ctime)
    results = steepleparse.parse(config['system']['logfile'])
    result_set_hash = create_revision_hash()

    trsc = TreeherderResultSetCollection()
    trs = trsc.get_resultset()

    trs.add_revision_hash(result_set_hash)
    trs.add_author('Firefox Nightly')
    trs.add_push_timestamp(push_time)

    tr = trs.get_revision()

    tr.add_revision(app_revision)
    tr.add_author('Firefox Nightly')
    tr.add_comment(build_version)
    tr.add_files([os.path.basename(f) for f in files])
    tr.add_repository(app_repository)

    trs.add_revision(tr)
    trsc.add(trs)

    tjc = TreeherderJobCollection()
    tj = tjc.get_job()

    tj.add_revision_hash(result_set_hash)
    tj.add_project(config['repo']['project'])
    tj.add_job_guid(str(uuid.uuid4()))

    tj.add_group_name('WebRTC QA Tests')
    tj.add_group_symbol('WebRTC')
    tj.add_job_name('Endurance')
    tj.add_job_symbol('end')

    tj.add_build_info('linux', 'linux64', 'x86_64')
    tj.add_machine_info('linux', 'linux64', 'x86_64')
    tj.add_description('WebRTC Sunny Day')
    tj.add_option_collection({'opt': True})  # must not be {}!
    tj.add_reason('testing')
    tj.add_who('Mozilla Platform QA')


    tj.add_submit_timestamp(submit_time)
    tj.add_start_timestamp(start_time)
    tj.add_end_timestamp(end_time)

    tj.add_state('completed')
    tj.add_machine(socket.gethostname())

    result_string = get_result_string(results)
    tj.add_result(result_string)
    if result_string != 'busted': 
        summary = get_result_summary(results)
        tj.add_artifact('Job Info', 'json', summary)
    
    tj.add_artifact('Results', 'json', results)

    tjc.add(tj)

    print 'trsc = ' + json.dumps(json.loads(trsc.to_json()), sort_keys=True,
                                 indent=4, separators=(',', ': '))

    print 'tjc = ' + json.dumps(json.loads(tjc.to_json()), sort_keys=True,
                                indent=4, separators=(',', ': '))

    req = TreeherderRequest(
        protocol='http',
        host=config['repo']['host'],
        project=config['repo']['project'],
        oauth_key=config['credentials']['key'],
        oauth_secret=config['credentials']['secret']
    )

    req.post(trsc)
    req.post(tjc)
예제 #19
0
    def post_to_treeherder(self, script, treeherder_url):
        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()
        job.add_group_name(self.device_properties['name'])
        job.add_group_symbol(self.device_properties['symbol'])
        job.add_job_name('Orangutan Monkey Script (%s)' %
                         self.device_properties.get('symbol'))
        job.add_job_symbol('Om')

        # Determine revision hash from application revision
        revision = self.version['application_changeset']
        project = self.version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            treeherder_url, 'api/project/%s/revision-lookup/?revision=%s' %
            (project, revision))
        self._logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')
        job.add_result(self.runner.crashed and 'testfailed' or 'success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        if self.is_debug:
            job.add_option_collection({'debug': True})
        else:
            job.add_option_collection({'opt': True})

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type':
            'link',
            'title':
            'Gaia revision:',
            'url':
            'https://github.com/mozilla-b2g/gaia/commit/%s' %
            self.version.get('gaia_changeset'),
            'value':
            self.version.get('gaia_changeset'),
        }, {
            'content_type':
            'text',
            'title':
            'Gaia date:',
            'value':
            self.version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(
                    self.version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': self.version.get('device_id')
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (date):',
            'value':
            self.version.get('device_firmware_date') and time.strftime(
                date_format,
                time.localtime(int(self.version.get('device_firmware_date')))),
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (incremental):',
            'value':
            self.version.get('device_firmware_version_incremental')
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (release):',
            'value':
            self.version.get('device_firmware_version_release')
        }]

        ci_url = os.environ.get('BUILD_URL')
        if ci_url:
            job_details.append({
                'url': ci_url,
                'value': ci_url,
                'content_type': 'link',
                'title': 'CI build:'
            })

        # Attach log files
        handlers = [
            handler for handler in self._logger.handlers
            if isinstance(handler, StreamHandler)
            and os.path.exists(handler.stream.name)
        ]
        for handler in handlers:
            path = handler.stream.name
            filename = os.path.split(path)[-1]
            try:
                url = self.upload_to_s3(path)
                job_details.append({
                    'url': url,
                    'value': filename,
                    'content_type': 'link',
                    'title': 'Log:'
                })
                # Add log reference
                if type(handler.formatter) is TbplFormatter or \
                        type(handler.formatter) is LogLevelFilter and \
                        type(handler.formatter.inner) is TbplFormatter:
                    job.add_log_reference(filename, url)
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload %s' % filename,
                    'content_type': 'text',
                    'title': 'Error:'
                })

        # Attach script
        filename = os.path.split(script)[-1]
        try:
            url = self.upload_to_s3(script)
            job_details.append({
                'url': url,
                'value': filename,
                'content_type': 'link',
                'title': 'Script:'
            })
        except S3UploadError:
            job_details.append({
                'value': 'Failed to upload %s' % filename,
                'content_type': 'text',
                'title': 'Error:'
            })

        # Attach logcat
        filename = '%s.log' % self.runner.device.dm._deviceSerial
        path = os.path.join(self.temp_dir, filename)
        try:
            url = self.upload_to_s3(path)
            job_details.append({
                'url': url,
                'value': filename,
                'content_type': 'link',
                'title': 'Logcat:'
            })
        except S3UploadError:
            job_details.append({
                'value': 'Failed to upload %s' % filename,
                'content_type': 'text',
                'title': 'Error:'
            })

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        # Attach crash dumps
        if self.runner.crashed:
            crash_dumps = os.listdir(self.crash_dumps_path)
            for filename in crash_dumps:
                path = os.path.join(self.crash_dumps_path, filename)
                try:
                    url = self.upload_to_s3(path)
                    job_details.append({
                        'url': url,
                        'value': filename,
                        'content_type': 'link',
                        'title': 'Crash:'
                    })
                except S3UploadError:
                    job_details.append({
                        'value': 'Failed to upload %s' % filename,
                        'content_type': 'text',
                        'title': 'Error:'
                    })

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=os.environ.get('TREEHERDER_KEY'),
            oauth_secret=os.environ.get('TREEHERDER_SECRET'))
        self._logger.info('Sending results to Treeherder: %s' % treeherder_url)
        self._logger.debug('Job collection: %s' % job_collection.to_json())
        response = request.post(job_collection)
        if response.status == 200:
            self._logger.debug('Response: %s' % response.read())
            self._logger.info(
                'Results are available to view at: %s' % (urljoin(
                    treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' %
                    (project, revision))))
        else:
            self._logger.error('Failed to send results to Treeherder! '
                               'Response: %s' % response.read())
예제 #20
0
    def submit_complete(self, test=None,
                        test_status=None,
                        test_message=None):
        """Submit test results for the worker's current job to Treeherder.

        submit_complete operates in two modes:

        * To report an infrastructure error which has prevented any of the
          tests from running.

          In this case, the test argument is None and the test_status
          and test_message will be used to report the error for each
          of the tests defined for the worker using
          PhoneTest.add_failure.

        * To report the status of an individual test.

          In this case, the test argument references a test object and
          both test_status and test_message are required to be
          None. The Treeherder test_status is determined by whether
          there were any failures reported.

        :param test: test to be reported.
        :param test_status: global test status to be reported.
        :param test_message: global test message to be reported.

        """
        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_complete: %s' % test)

        assert((test is None and test_status and test_message and
                test_status != PhoneTestResult.SUCCESS) or
               (test is not None and test_status is None and test_message is None))

        if not self.url or not self.worker.build.revision_hash:
            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_complete: no url/revision hash')
            return

        tjc = TreeherderJobCollection()

        if test:
            tests = [test]
        else:
            tests = self.worker.runnable_tests
            for t in tests:
                t.test_result.add_failure(t.name, test_status, test_message)

        for t in tests:
            self.worker.loggerdeco.debug('AutophoneTreeherder.submit_complete '
                                         'for %s %s' % (t.name, t.build.tree))

            t.end_timestamp = timestamp_now()
            # A usercancelled job may not have a start_timestamp
            # since it may have been cancelled before it started.
            if not t.start_timestamp:
                t.start_timestamp = t.end_timestamp

            if t.test_result.failed == 0:
                test_status = PhoneTestResult.SUCCESS
                failed = '0'
            else:
                if not test_status:
                    test_status = PhoneTestResult.TESTFAILED
                failed = '<em class="testfail">%s</em>' % t.test_result.failed

            t.job_details.append({
                'value': "%s/%s/%s" % (t.test_result.passed, failed, t.test_result.todo),
                'content_type': 'raw_html',
                'title': "%s-%s" % (t.job_name, t.job_symbol)
            })

            bug_suggestions = self.get_bug_suggestions(t.test_result.failures)

            if hasattr(t, 'phonedash_url'):
                t.job_details.append({
                    'url': t.phonedash_url,
                    'value': 'graph',
                    'content_type': 'link',
                    'title': 'phonedash:'
                    })

            tj = tjc.get_job()

            # Attach logs
            if self.worker.s3_bucket:
                # We must make certain that S3 keys for uploaded files
                # are unique. We can create a unique log_identifier as
                # follows: For Unittests, t._log's basename contains a
                # unique name based on the actual Unittest name, chunk
                # and phone id. For Non-Unittests, the test classname,
                # chunk and phone id can be used.

                if t._log:
                    log_identifier = os.path.splitext(os.path.basename(t._log))[0]
                else:
                    log_identifier = "%s-%s-%s" % (t.name, t.chunk, t.phone.id)

                key_prefix = os.path.dirname(
                    urlparse.urlparse(self.worker.build.url).path)
                key_prefix = re.sub('/tmp$', '', key_prefix)

                # Logcat
                fname = '%s-logcat.log' % log_identifier
                lname = 'logcat'
                with tempfile.NamedTemporaryFile(suffix='logcat.txt') as f:
                    for line in t.logcat.get(full=True):
                        f.write('%s\n' % line)
                    try:
                        url = self.worker.s3_bucket.upload(f.name, "%s/%s" % (
                            key_prefix, fname))
                        t.job_details.append({
                            'url': url,
                            'value': lname,
                            'content_type': 'link',
                            'title': 'artifact uploaded:'})
                    except S3Error:
                        self.worker.loggerdeco.exception('Error uploading logcat %s' % fname)
                        t.job_details.append({
                            'value': 'Failed to upload %s' % fname,
                            'content_type': 'text',
                            'title': 'Error:'})
                # UnitTest Log
                if t._log:
                    logfile = os.path.basename(t._log)
                    try:
                        url = self.worker.s3_bucket.upload(t._log, "%s/%s" % (
                            key_prefix, logfile))
                        t.job_details.append({
                            'url': url,
                            'value': logfile,
                            'content_type': 'link',
                            'title': 'artifact uploaded:'})
                        # don't add log reference  since we don't
                        # use treeherder's log parsing.
                        #tj.add_log_reference(logfile, url)
                    except S3Error:
                        self.worker.loggerdeco.exception('Error uploading log %s' % logfile)
                        t.job_details.append({
                            'value': 'Failed to upload log %s' % logfile,
                            'content_type': 'text',
                            'title': 'Error:'})
                # Upload directory containing ANRs, tombstones and other items
                # to be uploaded.
                if t.upload_dir:
                    for f in glob.glob(os.path.join(t.upload_dir, '*')):
                        try:
                            lname = os.path.basename(f)
                            fname = '%s-%s' % (log_identifier, lname)
                            url = self.worker.s3_bucket.upload(f, "%s/%s" % (
                                key_prefix, fname))
                            t.job_details.append({
                                'url': url,
                                'value': lname,
                                'content_type': 'link',
                                'title': 'artifact uploaded:'})
                        except S3Error:
                            self.worker.loggerdeco.exception('Error uploading artifact %s' % fname)
                            t.job_details.append({
                                'value': 'Failed to upload artifact %s' % fname,
                                'content_type': 'text',
                                'title': 'Error:'})

                # Since we are submitting results to Treeherder, we flush
                # the worker's log before uploading the log to
                # Treeherder. When we upload the log, it will contain
                # results for a single test run with possibly an error
                # message from the previous test if the previous log
                # upload failed.
                self.worker.filehandler.flush()
                logfile = self.worker.logfile
                fname = 'autophone-%s.log' % log_identifier
                lname = 'Autophone Log'
                try:
                    url = self.worker.s3_bucket.upload(
                        logfile, "%s/%s" % (key_prefix, fname))
                    t.job_details.append({
                        'url': url,
                        'value': lname,
                        'content_type': 'link',
                        'title': 'artifact uploaded:'})
                except S3Error:
                    self.worker.loggerdeco.exception('Error uploading %s' % fname)
                    t.job_details.append({
                        'value': 'Failed to upload Autophone log',
                        'content_type': 'text',
                        'title': 'Error:'})

            tj.add_revision_hash(self.worker.build.revision_hash)
            tj.add_project(self.worker.build.tree)
            tj.add_job_guid(t.job_guid)
            tj.add_job_name(t.job_name)
            tj.add_job_symbol(t.job_symbol)
            tj.add_group_name(t.group_name)
            tj.add_group_symbol(t.group_symbol)
            tj.add_product_name('fennec')
            tj.add_state(TestState.COMPLETED)
            tj.add_result(test_status)
            tj.add_submit_timestamp(t.submit_timestamp)
            tj.add_start_timestamp(t.start_timestamp)
            tj.add_end_timestamp(t.end_timestamp)
            tj.add_machine(t.phone.id)
            tj.add_build_url(self.worker.build.url)
            tj.add_build_info('android', t.phone.platform, t.phone.architecture)
            tj.add_machine_info('android',t.phone.platform, t.phone.architecture)
            tj.add_option_collection({'opt': True})
            tj.add_artifact('Job Info', 'json', {'job_details': t.job_details})
            if bug_suggestions:
                tj.add_artifact('Bug suggestions', 'json', bug_suggestions)

            tj.add_artifact('buildapi', 'json', {'buildername': t.buildername})
            tjc.add(tj)

            message = '%s %s %s TestResult: %s' % (self.worker.build.tree,
                                                   self.worker.build.id,
                                                   t.name, test_status)
            if t.message:
                message += ', %s' % t.message
            self.worker.loggerdeco.info(message)

        self.worker.loggerdeco.debug('AutophoneTreeherder.submit_completed: tjc: %s' %
                                     tjc.to_json())

        self.post_request(tjc)
예제 #21
0
파일: treeherder.py 프로젝트: bhirawat/gaia
    def post_to_treeherder(self, tests):
        self.logger.info('\nTREEHERDER\n----------')
        version = mozversion.get_version(
            binary=self.bin, sources=self.sources,
            dm_type='adb', device_serial=self.device_serial)

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get('device_id')
        if not device:
            self.logger.error('Submitting to Treeherder is currently limited '
                              'to devices.')
            return

        try:
            group = DEVICE_GROUP_MAP[device]
            job.add_group_name(group['name'])
            job.add_group_symbol(group['symbol'])
            job.add_job_name('Gaia Python Integration Test (%s)' % device)
            job.add_job_symbol('Gip')
        except KeyError:
            self.logger.error('Unknown device id: %s, unable to determine '
                              'Treeherder group. Supported device ids: %s' % (
                                  device, DEVICE_GROUP_MAP.keys()))
            return

        # Determine revision hash from application revision
        revision = version['application_changeset']
        project = version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            self.treeherder_url,
            'api/project/%s/revision-lookup/?revision=%s' % (
                project, revision))
        self.logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result('testfailed')
        else:
            job.add_result('success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        # All B2G device builds are currently opt builds
        job.add_option_collection({'opt': True})

        # TODO: Add log reference
        # job.add_log_reference()

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type': 'link',
            'title': 'Gaia revision:',
            'url': 'https://github.com/mozilla-b2g/gaia/commit/%s' %
                   version.get('gaia_changeset'),
            'value': version.get('gaia_changeset'),
        }, {
            'content_type': 'text',
            'title': 'Gaia date:',
            'value': version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': version.get('device_id')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (date):',
            'value': version.get('device_firmware_date') and time.strftime(
                date_format, time.localtime(int(
                    version.get('device_firmware_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device firmware (incremental):',
            'value': version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': version.get('device_firmware_version_release')
        }]

        if self.ci_url:
            job_details.append({
                'url': self.ci_url,
                'value': self.ci_url,
                'content_type': 'link',
                'title': 'CI build:'})

        artifacts = [self.html_output, self.xml_output]
        if any(artifacts):
            required_envs = ['AWS_ACCESS_KEY_ID', 'AWS_SECRET_ACCESS_KEY']
            upload_artifacts = all([os.environ.get(v) for v in required_envs])

            if upload_artifacts:
                conn = boto.connect_s3()
                bucket = conn.create_bucket(
                    os.environ.get('S3_UPLOAD_BUCKET', 'gaiatest'))
                bucket.set_acl('public-read')

                for artifact in artifacts:
                    if artifact and os.path.exists(artifact):
                        h = hashlib.sha512()
                        with open(artifact, 'rb') as f:
                            for chunk in iter(lambda: f.read(1024 ** 2), b''):
                                h.update(chunk)
                        _key = h.hexdigest()
                        key = bucket.get_key(_key)
                        if not key:
                            key = bucket.new_key(_key)
                        key.set_contents_from_filename(artifact)
                        key.set_acl('public-read')
                        blob_url = key.generate_url(expires_in=0,
                                                    query_auth=False)
                        job_details.append({
                            'url': blob_url,
                            'value': artifact,
                            'content_type': 'link',
                            'title': 'Artifact:'})
                        self.logger.info('Artifact %s uploaded to: %s' % (
                            artifact, blob_url))
            else:
                self.logger.info(
                    'Artifacts will not be included with the report. Please '
                    'set the following environment variables to enable '
                    'uploading of artifacts: %s' % ', '.join([
                        v for v in required_envs if not os.environ.get(v)]))

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=self.treeherder_key,
            oauth_secret=self.treeherder_secret)
        self.logger.debug('Sending results to Treeherder: %s' %
                          job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug('Response: %s' % response.read())
        assert response.status == 200, 'Failed to send results!'
        self.logger.info('Results are available to view at: %s' % (
            urljoin(self.treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' % (
                project, revision))))
예제 #22
0
    def post_to_treeherder(self, script, treeherder_url):
        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()
        job.add_group_name(self.device_properties['name'])
        job.add_group_symbol(self.device_properties['symbol'])
        job.add_job_name('Orangutan Monkey Script (%s)' %
                         self.device_properties.get('symbol'))
        job.add_job_symbol('Om')

        # Determine revision hash from application revision
        revision = self.version['application_changeset']
        project = self.version['application_repository'].split('/')[-1]
        lookup_url = urljoin(treeherder_url,
                             'api/project/%s/revision-lookup/?revision=%s' % (
                                 project, revision))
        self._logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')
        job.add_result(self.runner.crashed and 'testfailed' or 'success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        if self.is_debug:
            job.add_option_collection({'debug': True})
        else:
            job.add_option_collection({'opt': True})

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type': 'link',
            'title': 'Gaia revision:',
            'url': 'https://github.com/mozilla-b2g/gaia/commit/%s' %
                   self.version.get('gaia_changeset'),
            'value': self.version.get('gaia_changeset'),
        }, {
            'content_type': 'text',
            'title': 'Gaia date:',
            'value': self.version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(
                    self.version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': self.version.get('device_id')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (date):',
            'value': self.version.get('device_firmware_date') and
                     time.strftime(date_format, time.localtime(int(
                         self.version.get('device_firmware_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device firmware (incremental):',
            'value': self.version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': self.version.get('device_firmware_version_release')
        }]

        ci_url = os.environ.get('BUILD_URL')
        if ci_url:
            job_details.append({
                'url': ci_url,
                'value': ci_url,
                'content_type': 'link',
                'title': 'CI build:'})

        # Attach log files
        handlers = [handler for handler in self._logger.handlers
                    if isinstance(handler, StreamHandler) and
                    os.path.exists(handler.stream.name)]
        for handler in handlers:
            path = handler.stream.name
            filename = os.path.split(path)[-1]
            try:
                url = self.upload_to_s3(path)
                job_details.append({
                    'url': url,
                    'value': filename,
                    'content_type': 'link',
                    'title': 'Log:'})
                # Add log reference
                if type(handler.formatter) is TbplFormatter or \
                        type(handler.formatter) is LogLevelFilter and \
                        type(handler.formatter.inner) is TbplFormatter:
                    job.add_log_reference(filename, url)
            except S3UploadError:
                job_details.append({
                    'value': 'Failed to upload %s' % filename,
                    'content_type': 'text',
                    'title': 'Error:'})

        # Attach script
        filename = os.path.split(script)[-1]
        try:
            url = self.upload_to_s3(script)
            job_details.append({
                'url': url,
                'value': filename,
                'content_type': 'link',
                'title': 'Script:'})
        except S3UploadError:
            job_details.append({
                'value': 'Failed to upload %s' % filename,
                'content_type': 'text',
                'title': 'Error:'})

        # Attach logcat
        filename = '%s.log' % self.runner.device.dm._deviceSerial
        path = os.path.join(self.temp_dir, filename)
        try:
            url = self.upload_to_s3(path)
            job_details.append({
                'url': url,
                'value': filename,
                'content_type': 'link',
                'title': 'Logcat:'})
        except S3UploadError:
            job_details.append({
                'value': 'Failed to upload %s' % filename,
                'content_type': 'text',
                'title': 'Error:'})

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        # Attach crash dumps
        if self.runner.crashed:
            crash_dumps = os.listdir(self.crash_dumps_path)
            for filename in crash_dumps:
                path = os.path.join(self.crash_dumps_path, filename)
                try:
                    url = self.upload_to_s3(path)
                    job_details.append({
                        'url': url,
                        'value': filename,
                        'content_type': 'link',
                        'title': 'Crash:'})
                except S3UploadError:
                    job_details.append({
                        'value': 'Failed to upload %s' % filename,
                        'content_type': 'text',
                        'title': 'Error:'})

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(treeherder_url)
        request = TreeherderRequest(
            protocol=url.scheme,
            host=url.hostname,
            project=project,
            oauth_key=os.environ.get('TREEHERDER_KEY'),
            oauth_secret=os.environ.get('TREEHERDER_SECRET'))
        self._logger.info('Sending results to Treeherder: %s' % treeherder_url)
        self._logger.debug('Job collection: %s' %
                           job_collection.to_json())
        response = request.post(job_collection)
        if response.status == 200:
            self._logger.debug('Response: %s' % response.read())
            self._logger.info('Results are available to view at: %s' % (
                urljoin(treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' % (
                    project, revision))))
        else:
            self._logger.error('Failed to send results to Treeherder! '
                               'Response: %s' % response.read())
예제 #23
0
def main():
    result_revision_hash = create_revision_hash()

    trsc = TreeherderResultSetCollection()

    trs = trsc.get_resultset()

    # self.required_properties = {
    #     'revision_hash':{ 'len':50, 'cb':self.validate_existence },
    #     'revisions':{ 'type':list, 'cb':self.validate_existence },
    #     'author':{ 'len':150, 'cb':self.validate_existence }
    #     }

    trs.add_revision_hash(result_revision_hash)
    trs.add_author('WebRTC QA Tests')
    trs.add_push_timestamp(int(time.time()))

    tr = trs.get_revision()

    # self.required_properties = {
    #     'revision':{ 'len':50, 'cb':self.validate_existence },
    #     'repository':{ 'cb':self.validate_existence },
    #     'files':{ 'type':list, 'cb':self.validate_existence },
    #     }

    tr.add_revision(create_revision_hash()[:12])
    tr.add_author('Firefox Nightly')
    tr.add_comment('firefox-33.0a1.en-US')
    tr.add_files(['firefox-33.0a1.en-US.linux-i686.tar.bz2',
                  'firefox-33.0a1.en-US.linux-x86_64.tests.zip'])
    tr.add_repository(
        'ftp://ftp.mozilla.org/pub/firefox/nightly/latest-mozilla-central/')
    trs.add_revision(tr)

    trsc.add(trs)

    tjc = TreeherderJobCollection()
    tj = tjc.get_job()

    # self.required_properties = {
    #     'revision_hash':{ 'len':50, 'cb':self.validate_existence },
    #     'project':{ 'cb':self.validate_existence },
    #     'job':{ 'type':dict, 'cb':self.validate_existence },
    #     'job.job_guid':{ 'len':50, 'cb':self.validate_existence }
    # }

    tj.add_revision_hash(result_revision_hash)
    tj.add_project('qa-try')
    tj.add_job_guid(str(uuid.uuid4()))

    tj.add_build_info('linux', 'linux64', 'x86_64')
    tj.add_description('WebRTC Sunny Day')
    tj.add_machine_info('linux', 'linux64', 'x86_64')
    tj.add_end_timestamp(int(time.time()) - 5)
    tj.add_start_timestamp(int(time.time()) - 3600 * 3 - 5)
    tj.add_submit_timestamp(int(time.time()) - 3600 * 3 - 10)
    tj.add_state('completed')
    tj.add_machine('webrtc-server')
    tj.add_option_collection({'opt': True})  # must not be {}!
    tj.add_reason('testing')
    tj.add_result('success')  # must be success/testfailed/busted
    tj.add_who('*****@*****.**')
    tj.add_group_name('WebRTC QA Tests')
    tj.add_group_symbol('WebRTC')
    tj.add_job_symbol('end')
    tj.add_job_name('Endurance')

    tj.add_artifact('Job Info', 'json', {
        "job_details": [
            {
                'title': 'Iterations:',
                'value': '10782',
                'content_type': 'text'
            },
            {
                'title': 'Errors:',
                'value': '5',
                'content_type': 'text'
            },
            {
                'title': 'Longest Pass Duration:',
                'value': '2:58:36.5',
                'content_type': 'text'
            }
        ],
    })

    tjc.add(tj)

    key, secret = get_oauth_creds()
    project, host = get_repo_details()

    req = TreeherderRequest(
        protocol='http',
        host=host,
        project=project,
        oauth_key=key,
        oauth_secret=secret
    )

    print 'trsc = ' + json.dumps(json.loads(trsc.to_json()), sort_keys=True,
                                 indent=4, separators=(',', ': '))

    print 'tjc = ' + json.dumps(json.loads(tjc.to_json()), sort_keys=True,
                                indent=4, separators=(',', ': '))

    # print 'req.oauth_key = ' + req.oauth_key
    # print 'req.oauth_secret = ' + req.oauth_secret

    # uri = req.get_uri(trsc)
    # print 'req.get_uri() = ' + uri
    # print 'req.oauth_client.get_signed_uri() = ' +
    # req.oauth_client.get_signed_uri(trsc.to_json(), uri)

    req.post(trsc)
    req.post(tjc)
예제 #24
0
    def submit_pending(self, machine, build_url, project, revision_hash, tests=[]):
        """Submit tests pending notifications to Treeherder

        :param machine: machine id
        :param build_url: url to build being tested.
        :param project: repository of build.
        :param revision_hash: Treeherder revision hash of build.
        :param tests: Lists of tests to be reported.
        """
        logger.debug('AutophoneTreeherder.submit_pending: %s' % tests)
        if not self.url or not revision_hash:
            logger.debug('AutophoneTreeherder.submit_pending: no url/revision hash')
            return

        tjc = TreeherderJobCollection(job_type='update')

        for t in tests:
            t.message = None
            t.submit_timestamp = timestamp_now()
            t.job_details = []

            logger.info('creating Treeherder job %s for %s %s, '
                        'revision_hash: %s' % (
                            t.job_guid, t.name, project,
                            revision_hash))

            logger.debug('AutophoneTreeherder.submit_pending: '
                         'test config_file=%s, config sections=%s' % (
                             t.config_file, t.cfg.sections()))

            tj = tjc.get_job()
            tj.add_revision_hash(revision_hash)
            tj.add_project(project)
            tj.add_job_guid(t.job_guid)
            tj.add_job_name(t.job_name)
            tj.add_job_symbol(t.job_symbol)
            tj.add_group_name(t.group_name)
            tj.add_group_symbol(t.group_symbol)
            tj.add_product_name('fennec')
            tj.add_state(TestState.PENDING)
            tj.add_submit_timestamp(t.submit_timestamp)
            # XXX need to send these until Bug 1066346 fixed.
            tj.add_start_timestamp(t.submit_timestamp)
            tj.add_end_timestamp(t.submit_timestamp)
            #
            tj.add_machine(machine)
            tj.add_build_url(build_url)
            tj.add_build_info('android', t.phone.platform, t.phone.architecture)
            tj.add_machine_info('android',t.phone.platform, t.phone.architecture)
            tj.add_option_collection({'opt': True})

            # Fake the buildername from buildbot...
            tj.add_artifact('buildapi', 'json', {
                'buildername': t.get_buildername(project)})
            # Create a 'privatebuild' artifact for storing information
            # regarding the build.
            tj.add_artifact('privatebuild', 'json', {
                'build_url': build_url,
                'config_file': t.config_file,
                'chunk': t.chunk})
            tjc.add(tj)

        logger.debug('AutophoneTreeherder.submit_pending: tjc: %s' % (
            tjc.to_json()))

        self.post_request(machine, project, tjc)
예제 #25
0
파일: treeherder.py 프로젝트: yurenju/gaia
    def post_to_treeherder(self, tests):
        self.logger.info('\nTREEHERDER\n----------')
        version = mozversion.get_version(binary=self.bin,
                                         sources=self.sources,
                                         dm_type='adb',
                                         device_serial=self.device_serial)

        job_collection = TreeherderJobCollection()
        job = job_collection.get_job()

        device = version.get('device_id')
        if not device:
            self.logger.error('Submitting to Treeherder is currently limited '
                              'to devices.')
            return

        try:
            group = DEVICE_GROUP_MAP[device]
            job.add_group_name(group['name'])
            job.add_group_symbol(group['symbol'])
            job.add_job_name('Gaia Python Integration Test (%s)' % device)
            job.add_job_symbol('Gip')
        except KeyError:
            self.logger.error('Unknown device id: %s, unable to determine '
                              'Treeherder group. Supported device ids: %s' %
                              (device, DEVICE_GROUP_MAP.keys()))
            return

        # Determine revision hash from application revision
        revision = version['application_changeset']
        project = version['application_repository'].split('/')[-1]
        lookup_url = urljoin(
            self.treeherder_url,
            'api/project/%s/revision-lookup/?revision=%s' %
            (project, revision))
        self.logger.debug('Getting revision hash from: %s' % lookup_url)
        response = requests.get(lookup_url)
        response.raise_for_status()
        assert response.json(), 'Unable to determine revision hash for %s. ' \
                                'Perhaps it has not been ingested by ' \
                                'Treeherder?' % revision
        revision_hash = response.json()[revision]['revision_hash']
        job.add_revision_hash(revision_hash)
        job.add_project(project)
        job.add_job_guid(str(uuid.uuid4()))
        job.add_product_name('b2g')
        job.add_state('completed')

        # Determine test result
        if self.failed or self.unexpected_successes:
            job.add_result('testfailed')
        else:
            job.add_result('success')

        job.add_submit_timestamp(int(self.start_time))
        job.add_start_timestamp(int(self.start_time))
        job.add_end_timestamp(int(self.end_time))

        job.add_machine(socket.gethostname())
        job.add_build_info('b2g', 'b2g-device-image', 'x86')
        job.add_machine_info('b2g', 'b2g-device-image', 'x86')

        # All B2G device builds are currently opt builds
        job.add_option_collection({'opt': True})

        # TODO: Add log reference
        # job.add_log_reference()

        date_format = '%d %b %Y %H:%M:%S'
        job_details = [{
            'content_type':
            'link',
            'title':
            'Gaia revision:',
            'url':
            'https://github.com/mozilla-b2g/gaia/commit/%s' %
            version.get('gaia_changeset'),
            'value':
            version.get('gaia_changeset'),
        }, {
            'content_type':
            'text',
            'title':
            'Gaia date:',
            'value':
            version.get('gaia_date') and time.strftime(
                date_format, time.localtime(int(version.get('gaia_date')))),
        }, {
            'content_type': 'text',
            'title': 'Device identifier:',
            'value': version.get('device_id')
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (date):',
            'value':
            version.get('device_firmware_date') and time.strftime(
                date_format,
                time.localtime(int(version.get('device_firmware_date')))),
        }, {
            'content_type':
            'text',
            'title':
            'Device firmware (incremental):',
            'value':
            version.get('device_firmware_version_incremental')
        }, {
            'content_type': 'text',
            'title': 'Device firmware (release):',
            'value': version.get('device_firmware_version_release')
        }]

        if self.ci_url:
            job_details.append({
                'url': self.ci_url,
                'value': self.ci_url,
                'content_type': 'link',
                'title': 'CI build:'
            })

        if job_details:
            job.add_artifact('Job Info', 'json', {'job_details': job_details})

        # TODO: Add XML/HTML reports as artifacts
        # job.add_artifact()

        job_collection.add(job)

        # Send the collection to Treeherder
        url = urlparse(self.treeherder_url)
        request = TreeherderRequest(protocol=url.scheme,
                                    host=url.hostname,
                                    project=project,
                                    oauth_key=self.treeherder_key,
                                    oauth_secret=self.treeherder_secret)
        self.logger.debug('Sending results to Treeherder: %s' %
                          job_collection.to_json())
        response = request.post(job_collection)
        self.logger.debug('Response: %s' % response.read())
        assert response.status == 200, 'Failed to send results!'
        self.logger.info(
            'Results are available to view at: %s' %
            (urljoin(self.treeherder_url, '/ui/#/jobs?repo=%s&revision=%s' %
                     (project, revision))))