def test_reminder(self):
        """Test reminders."""
        # Return the same status for all build types.
        self.mock.get.return_value = MockResponse(
            json.dumps({
                'projects': [
                    {
                        'history': [{
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj0-id',
                            'success': False
                        }],
                        'name':
                        'proj0',
                    },
                    {
                        'history': [{
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj0-id',
                            'success': False
                        }],
                        'name':
                        'proj1',
                    },
                ]
            }))

        data_types.OssFuzzProject(id='proj0', name='proj0',
                                  ccs=['*****@*****.**']).put()
        data_types.OssFuzzBuildFailure(
            id='proj0',
            project_name='proj0',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id='1',
            consecutive_failures=7,
            build_type='fuzzing').put()
        data_types.OssFuzzProject(id='proj1', name='proj1',
                                  ccs=['*****@*****.**']).put()
        data_types.OssFuzzBuildFailure(
            id='proj1',
            project_name='proj1',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id='2',
            consecutive_failures=3,
            build_type='fuzzing').put()

        self.itm.issues[1] = Issue()
        self.itm.issues[2] = Issue()

        self.app.get('/build-status')
        self.assertEqual(
            'Friendly reminder that the the build is still failing.\n'
            'Please try to fix this failure to ensure that fuzzing remains '
            'productive.\n'
            'Latest build log: https://oss-fuzz-build-logs.storage.googleapis.com/'
            'log-proj0-id.txt\n', self.itm.issues[1].comment)
        self.assertEqual('', self.itm.issues[2].comment)
def create_build_failure(failure, build_type):
    """Create new build failure."""
    return data_types.OssFuzzBuildFailure(
        id=_get_ndb_key(failure['name'], build_type),
        project_name=failure['name'],
        last_checked_timestamp=get_build_time(failure),
        build_type=build_type)
    def test_disabled_project(self):
        """Test disabled project."""
        # Return the same status for all build types.
        self.mock.get.return_value = MockResponse(
            json.dumps({
                'projects': [
                    {
                        'history': [{
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj2-id',
                            'success': False
                        }],
                        'name':
                        'disabled_proj',
                    },
                ]
            }))

        # Only fuzzing build type failure should be stored.
        data_types.OssFuzzBuildFailure(
            id='disabled_proj',
            project_name='disabled_proj',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            consecutive_failures=1,
            build_type='fuzzing').put()

        self.app.get('/build-status')
        six.assertCountEqual(self, [
            {
                'build_type': 'fuzzing',
                'consecutive_failures': 1,
                'issue_id': None,
                'last_checked_timestamp': datetime.datetime(2018, 1, 31, 0, 0),
                'project_name': u'disabled_proj',
            },
        ], [
            failure.to_dict()
            for failure in data_types.OssFuzzBuildFailure.query()
        ])

        self.assertEqual(0, len(self.itm.issues))
    def test_recovered_build_failure(self):
        """Test fixed build failures."""
        # Use the same status for all build types.
        self.mock.get.return_value = MockResponse(
            json.dumps({
                'successes': [
                    {
                        'name': 'proj0',
                        'finish_time': '2018-02-01T00:00:00.000000Z',
                        'build_id': 'proj0-id',
                    },
                ],
                'failures': [],
            }))

        data_types.OssFuzzBuildFailure(
            id='proj0',
            project_name='proj0',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id='1',
            consecutive_failures=2,
            build_type='fuzzing').put()

        issue = Issue()
        issue.open = True
        issue.add_label('Type-Build-Failure')
        issue.add_label('Proj-proj2')
        issue.summary = 'Build failure in proj2'
        issue.body = 'Build failure'

        self.itm.issues[1] = issue

        self.app.get('/build-status')
        self.assertEqual(0, data_types.OssFuzzBuildFailure.query().count())

        issue = self.itm.issues[1]
        self.assertFalse(issue.open)
        self.assertEqual('Verified', issue.status)
        self.assertEqual('The latest build has succeeded, closing this issue.',
                         issue.comment)
    def test_build_failures(self):
        """Test run with multiple build failures of different type."""
        def _mock_requests_get(url):
            """Mock requests.get."""
            if url == oss_fuzz_build_status.FUZZING_STATUS_URL:
                return MockResponse(
                    json.dumps({
                        'successes': [
                            # Both fuzzing and coverage build types are successful.
                            {
                                'name': 'proj0',
                                'finish_time': '2018-02-01T00:00:00.000000Z',
                                'build_id': 'proj0-id-f',
                            },

                            # Only coverage build type is broken for a while.
                            {
                                'name': 'proj5',
                                'finish_time': '2018-02-01T00:00:00.000000Z',
                                'build_id': 'proj5-id-f',
                            },

                            # Only coverage build type broken.
                            {
                                'name': 'proj6',
                                'finish_time': '2018-02-01T00:00:00.000000Z',
                                'build_id': 'proj6-id-f',
                            },
                        ],
                        'failures': [
                            # New failure (first 1).
                            {
                                'name': 'proj1',
                                'finish_time':
                                '2018-02-01T00:00:00.000000000Z',
                                'build_id': 'proj1-id-f',
                            },

                            # Seen failure (second consecutive).
                            {
                                'name': 'proj2',
                                'finish_time': '2018-02-01T00:00:00.000000Z',
                                'build_id': 'proj2-id-f',
                            },

                            # Seen failure (not updated).
                            {
                                'name': 'proj3',
                                'finish_time': '2018-01-31T00:00:00.000000Z',
                                'build_id': 'proj3-id-f',
                            },

                            # Seen failure (third consecutive, bug already filed).
                            {
                                'name': 'proj4',
                                'finish_time': '2018-02-01T00:00:00.000000Z',
                                'build_id': 'proj4-id-f',
                            },
                        ],
                    }))

            assert url == oss_fuzz_build_status.COVERAGE_STATUS_URL
            return MockResponse(
                json.dumps({
                    'successes': [
                        # Both fuzzing and coverage build types are successful.
                        {
                            'name': 'proj0',
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj0-id-c',
                        },
                    ],
                    'failures': [
                        # New failure (first 1).
                        {
                            'name': 'proj1',
                            'finish_time': '2018-02-01T00:00:00.000000000Z',
                            'build_id': 'proj1-id-c',
                        },

                        # Seen failure (second consecutive).
                        {
                            'name': 'proj2',
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj2-id-c',
                        },

                        # Seen failure (not updated).
                        {
                            'name': 'proj3',
                            'finish_time': '2018-01-31T00:00:00.000000Z',
                            'build_id': 'proj3-id-c',
                        },

                        # Seen failure (third consecutive, bug already filed).
                        {
                            'name': 'proj4',
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj4-id-c',
                        },

                        # Coverage build type is broken for a while.
                        {
                            'name': 'proj5',
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj5-id-c',
                        },

                        # Only coverage build type broken (second consecutive).
                        {
                            'name': 'proj6',
                            'finish_time': '2018-02-01T00:00:00.000000Z',
                            'build_id': 'proj6-id-c',
                        },
                    ],
                }))

        self.mock.get.side_effect = _mock_requests_get

        data_types.OssFuzzBuildFailure(
            id='proj2',
            project_name='proj2',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            consecutive_failures=1,
            build_type='fuzzing').put()

        data_types.OssFuzzBuildFailure(
            id='proj3',
            project_name='proj3',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            consecutive_failures=1,
            build_type='fuzzing').put()

        data_types.OssFuzzBuildFailure(
            id='proj4',
            project_name='proj4',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id='1337',
            consecutive_failures=2,
            build_type='fuzzing').put()

        data_types.OssFuzzBuildFailure(
            id='proj5-coverage',
            project_name='proj5',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id='31337',
            consecutive_failures=5,
            build_type='coverage').put()

        data_types.OssFuzzBuildFailure(
            id='proj6-coverage',
            project_name='proj6',
            last_checked_timestamp=datetime.datetime(2018, 1, 31),
            issue_id=None,
            consecutive_failures=1,
            build_type='coverage').put()

        data_types.OssFuzzProject(id='proj2', name='proj2',
                                  ccs=['*****@*****.**']).put()
        data_types.OssFuzzProject(id='proj6', name='proj7',
                                  ccs=['*****@*****.**']).put()

        self.app.get('/build-status')
        self.assertItemsEqual([
            {
                'build_type': 'fuzzing',
                'consecutive_failures': 1,
                'issue_id': None,
                'last_checked_timestamp': datetime.datetime(2018, 2, 1, 0, 0),
                'project_name': u'proj1'
            },
            {
                'build_type': 'fuzzing',
                'consecutive_failures': 2,
                'issue_id': '1',
                'last_checked_timestamp': datetime.datetime(2018, 2, 1, 0, 0),
                'project_name': u'proj2'
            },
            {
                'build_type': 'fuzzing',
                'consecutive_failures': 1,
                'issue_id': None,
                'last_checked_timestamp': datetime.datetime(2018, 1, 31, 0, 0),
                'project_name': u'proj3'
            },
            {
                'build_type': 'fuzzing',
                'consecutive_failures': 3,
                'issue_id': '1337',
                'last_checked_timestamp': datetime.datetime(2018, 2, 1, 0, 0),
                'project_name': u'proj4'
            },
            {
                'build_type': 'coverage',
                'consecutive_failures': 6,
                'issue_id': '31337',
                'last_checked_timestamp': datetime.datetime(2018, 2, 1, 0, 0),
                'project_name': u'proj5'
            },
            {
                'build_type': 'coverage',
                'consecutive_failures': 2,
                'issue_id': '2',
                'last_checked_timestamp': datetime.datetime(2018, 2, 1, 0, 0),
                'project_name': u'proj6'
            },
        ], [
            failure.to_dict()
            for failure in data_types.OssFuzzBuildFailure.query()
        ])

        self.assertEqual(2, len(self.itm.issues))
        issue = self.itm.issues[1]
        self.assertItemsEqual(['*****@*****.**'], issue.cc)
        self.assertEqual('New', issue.status)
        self.assertEqual('proj2: Build failure', issue.summary)
        self.assertEqual(
            'The last 2 builds for proj2 have been failing.\n'
            '<b>Build log:</b> '
            'https://oss-fuzz-build-logs.storage.googleapis.com/'
            'log-proj2-id-f.txt\n'
            'Build type: fuzzing\n\n'
            'To reproduce locally, please see: '
            'https://github.com/google/oss-fuzz/'
            'blob/master/docs/reproducing.md#reproducing-build-failures\n\n'
            'If you have any questions, please file a bug on '
            'https://github.com/google/oss-fuzz/issues/new.\n\n'
            '**This bug will be automatically closed within a '
            'day once it is fixed.**', issue.body)

        self.assertTrue(issue.has_label('Proj-proj2'))
        self.assertTrue(issue.has_label('Type-Build-Failure'))

        issue = self.itm.issues[2]
        self.assertItemsEqual(['*****@*****.**'], issue.cc)
        self.assertEqual('New', issue.status)
        self.assertEqual('proj6: Build failure', issue.summary)
        self.assertEqual(
            'The last 2 builds for proj6 have been failing.\n'
            '<b>Build log:</b> '
            'https://oss-fuzz-build-logs.storage.googleapis.com/'
            'log-proj6-id-c.txt\n'
            'Build type: coverage\n\n'
            'To reproduce locally, please see: '
            'https://github.com/google/oss-fuzz/'
            'blob/master/docs/reproducing.md#reproducing-build-failures\n\n'
            'If you have any questions, please file a bug on '
            'https://github.com/google/oss-fuzz/issues/new.\n\n'
            '**This bug will be automatically closed within a '
            'day once it is fixed.**', issue.body)

        self.assertTrue(issue.has_label('Proj-proj6'))
        self.assertTrue(issue.has_label('Type-Build-Failure'))