def test_poll_split_file(self): """Make sure split file works on branch only changes""" self.attachChangeSource( P4Source(p4port=None, p4user=None, p4base='//depot/myproject/', split_file=get_simple_split)) self.expectCommands( gpo.Expect( 'p4', 'changes', '//depot/myproject/...@51,#head').stdout(third_p4changes), ) self.add_p4_describe_result(5, p4change[5]) self.changesource.last_change = 50 yield self.changesource.poll() # when_timestamp is converted from a local time spec, so just # replicate that here when = self.makeTime("2006/04/13 21:55:39") def changeKey(change): """ Let's sort the array of changes by branch, because in P4Source._poll(), changeAdded() is called by iterating over a dictionary of branches""" return change['branch'] self.assertEqual(sorted(self.master.data.updates.changesAdded, key=changeKey), sorted([{ 'author': u'mpatel', 'branch': u'branch_c', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_c_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }, { 'author': u'mpatel', 'branch': u'branch_b', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_b_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }], key=changeKey)) self.assertEqual(self.changesource.last_change, 5) self.assertAllCommandsRan()
def check_second_check(res): # when_timestamp is converted from a local time spec, so just # replicate that here when1 = self.makeTime("2006/04/13 21:46:23") when2 = self.makeTime("2006/04/13 21:51:39") # these two can happen in either order, since they're from the same # perforce change. changesAdded = self.master.data.updates.changesAdded if changesAdded[1]['branch'] == 'branch_c': changesAdded[1:] = reversed(changesAdded[1:]) self.assertEqual(self.master.data.updates.changesAdded, [{ 'author': u'slamb', 'branch': u'trunk', 'category': None, 'codebase': None, 'comments': u'creation', 'files': [u'whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '2', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when1), }, { 'author': u'bob', 'branch': u'branch_b', 'category': None, 'codebase': None, 'comments': u'short desc truncated because this is a long description.\nASDF-GUI-P3-\u2018Upgrade Icon\u2019 disappears sometimes.', 'files': [u'branch_b_file', u'whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '3', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when2), }, { 'author': u'bob', 'branch': u'branch_c', 'category': None, 'codebase': None, 'comments': u'short desc truncated because this is a long description.\nASDF-GUI-P3-\u2018Upgrade Icon\u2019 disappears sometimes.', 'files': [u'whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '3', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when2), }]) self.assertAllCommandsRan()
def setup_thd(conn): metadata = sa.MetaData() metadata.bind = conn # This table contains basic information about each build. builds = sa.Table('builds', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column('number', sa.Integer, nullable=False), sa.Column('builderid', sa.Integer), # note that there is 1:N relationship here. # In case of worker loss, build has results RETRY # and buildrequest is unclaimed sa.Column('buildrequestid', sa.Integer, nullable=False), # worker which performed this build # TODO: ForeignKey to buildworkers table, named buildworkerid # TODO: keep nullable to support worker-free # builds sa.Column('buildworkerid', sa.Integer), # master which controlled this build sa.Column('masterid', sa.Integer, nullable=False), # start/complete times sa.Column( 'started_at', sa.Integer, nullable=False), sa.Column('complete_at', sa.Integer), # a list of strings describing the build's state sa.Column( 'state_strings_json', sa.Text, nullable=False), sa.Column('results', sa.Integer), ) builds.create() buildsets = sa.Table('buildsets', metadata, sa.Column('id', sa.Integer, primary_key=True), sa.Column( 'external_idstring', sa.String(256)), sa.Column('reason', sa.String(256)), sa.Column( 'submitted_at', sa.Integer, nullable=False), sa.Column('complete', sa.SmallInteger, nullable=False, server_default=sa.DefaultClause("0")), sa.Column('complete_at', sa.Integer), sa.Column('results', sa.SmallInteger), ) buildsets.create() conn.execute(buildsets.insert(), [ dict(external_idstring='extid', reason='rsn1', sourcestamps=[91], submitted_at=datetime2epoch( datetime.datetime(1978, 6, 15, 12, 31, 15)), complete_at=datetime2epoch( datetime.datetime(1979, 6, 15, 12, 31, 15)), complete=0, results=-1, bsid=91) ])
def formatData(self, data): if isinstance(data, dict): for key in data: if isinstance(data[key], datetime): data[key] = datetime2epoch(data[key]) elif type(data[key]) in (dict, list, tuple): data[key] = self.formatData(data[key]) elif type(data) in (list, tuple): for index in range(len(data)): if isinstance(data[index], datetime): data[index] = datetime2epoch(data[index]) elif type(data[index]) in (dict, list, tuple): data[index] = self.formatData(data[index]) return data
def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict["author"] change.comments = chdict["comments"] change.revision = chdict["revision"] change.branch = chdict["branch"] change.category = chdict["category"] change.revlink = chdict["revlink"] change.repository = chdict["repository"] change.codebase = chdict["codebase"] change.project = chdict["project"] change.number = chdict["changeid"] when = chdict["when_timestamp"] if when: when = datetime2epoch(when) change.when = when change.files = chdict["files"][:] change.files.sort() change.properties = Properties() for n, (v, s) in chdict["properties"].iteritems(): change.properties.setProperty(n, v, s) return defer.succeed(change)
def command_LAST(self, args): # FIXME: NEED TO THINK ABOUT! args = self.splitArgs(args) if len(args) == 0: builders = yield self.getAllBuilders() elif len(args) == 1: builder = yield self.getBuilder(buildername=args[0]) if not builder: raise UsageError("no such builder") builders = [builder] else: raise UsageError("try 'last <builder>'") for builder in builders: lastBuild = yield self.getLastCompletedBuild(builder['builderid']) if not lastBuild: status = "(no builds run since last restart)" else: complete_at = lastBuild['complete_at'] if complete_at: complete_at = util.datetime2epoch(complete_at) ago = self.convertTime(int(util.now() - complete_at)) else: ago = "??" status = lastBuild['state_string'] status = 'last build %s ago: %s' % (ago, status) self.send("last build [%s]: %s" % (builder['name'], status))
def _make_ch(cls, changeid, master, chdict): change = cls(None, None, None, _fromChdict=True) change.who = chdict['author'] change.comments = chdict['comments'] change.revision = chdict['revision'] change.branch = chdict['branch'] change.category = chdict['category'] change.revlink = chdict['revlink'] change.repository = chdict['repository'] change.codebase = chdict['codebase'] change.project = chdict['project'] change.number = chdict['changeid'] when = chdict['when_timestamp'] if when: when = datetime2epoch(when) change.when = when change.files = sorted(chdict['files']) change.properties = Properties() for n, (v, s) in iteritems(chdict['properties']): change.properties.setProperty(n, v, s) return defer.succeed(change)
def test_server_tz(self): """Verify that the server_tz parameter is handled correctly""" self.attachChangeSource( P4Source(p4port=None, p4user=None, p4base='//depot/myproject/', split_file=get_simple_split, server_tz="Europe/Berlin")) self.expectCommands( gpo.Expect( 'p4', 'changes', '//depot/myproject/...@51,#head').stdout(third_p4changes), ) self.add_p4_describe_result(5, p4change[5]) self.changesource.last_change = 50 yield self.changesource.poll() # when_timestamp is converted from 21:55:39 Berlin time to UTC when_berlin = self.makeTime("2006/04/13 21:55:39") when_berlin = when_berlin.replace( tzinfo=dateutil.tz.gettz('Europe/Berlin')) when = datetime2epoch(when_berlin) self.assertEqual([ch['when_timestamp'] for ch in self.master.data.updates.changesAdded], [when, when]) self.assertAllCommandsRan()
def insert_sourcestamps_changes(self, conn, sourcestampid, repository, codebase, changeid): conn.execute(self.sourcestamps.insert(), id=sourcestampid, sourcestampsetid=sourcestampid, branch='this_branch', revision='this_revision', patchid=None, repository=repository, project='', codebase=codebase) dt_when = datetime.datetime(1978, 6, 15, 12, 31, 15, tzinfo=UTC) conn.execute(self.changes.insert(), changeid=changeid, author='develop', comments='no comment', is_dir=0, branch='default', revision='FD56A89', revling=None, when_timestamp=datetime2epoch(dt_when), category=None, repository=repository, codebase=codebase, project='')
def addChange(self, author=None, files=None, comments=None, is_dir=0, revision=None, when_timestamp=None, branch=None, category=None, revlink='', properties={}, repository='', project='', codebase='', uid=None): if self.changes: changeid = max(self.changes.iterkeys()) + 1 else: changeid = 500 self.changes[changeid] = dict( changeid=changeid, author=author, comments=comments, is_dir=is_dir, revision=revision, when_timestamp=datetime2epoch(when_timestamp), branch=branch, category=category, revlink=revlink, repository=repository, project=project, codebase=codebase, files=files, properties=properties) return defer.succeed(changeid)
def completeBuildRequests(self, brids, results, complete_at=None, _reactor=reactor): assert results != RETRY, "a buildrequest cannot be completed with a retry status!" if complete_at is not None: complete_at = datetime2epoch(complete_at) else: complete_at = _reactor.seconds() def thd(conn): transaction = conn.begin() # the update here is simple, but a number of conditions are # attached to ensure that we do not update a row inappropriately, # Note that checking that the request is mine would require a # subquery, so for efficiency that is not checked. reqs_tbl = self.db.model.buildrequests # we'll need to batch the brids into groups of 100, so that the # parameter lists supported by the DBAPI aren't exhausted for batch in self.doBatch(brids, 100): q = reqs_tbl.update() q = q.where(reqs_tbl.c.id.in_(batch)) q = q.where(reqs_tbl.c.complete != 1) res = conn.execute(q, complete=1, results=results, complete_at=complete_at) # if an incorrect number of rows were updated, then we failed. if res.rowcount != len(batch): log.msg("tried to complete %d buildrequests, " "but only completed %d" % (len(batch), res.rowcount)) transaction.rollback() raise NotClaimedError transaction.commit() return self.db.pool.do(thd)
def _processChanges(self, page): result = json.loads(page, encoding=self.encoding) for pr in result["values"]: branch = pr["source"]["branch"]["name"] nr = int(pr["id"]) # Note that this is a short hash. The full length hash can be accessed via the # commit api resource but we want to avoid requesting multiple pages as long as # we are not sure that the pull request is new or updated. revision = pr["source"]["commit"]["hash"] # check branch if not self.branch or branch in self.branch: current = yield self._getCurrentRev(nr) if not current or current != revision: # parse pull request api page (required for the filter) page = yield client.getPage(str(pr["links"]["self"]["href"])) pr_json = json.loads(page, encoding=self.encoding) # filter pull requests by user function if not self.pullrequest_filter(pr_json): log.msg("pull request does not match filter") continue # access additional information author = pr["author"]["display_name"] prlink = pr["links"]["html"]["href"] # Get time updated time. Note that the timezone offset is ignored. if self.useTimestamps: updated = datetime.strptime(pr["updated_on"].split(".")[0], "%Y-%m-%dT%H:%M:%S") else: updated = epoch2datetime(reactor.seconds()) title = pr["title"] # parse commit api page page = yield client.getPage(str(pr["source"]["commit"]["links"]["self"]["href"])) commit_json = json.loads(page, encoding=self.encoding) # use the full-length hash from now on revision = commit_json["hash"] revlink = commit_json["links"]["html"]["href"] # parse repo api page page = yield client.getPage(str(pr["source"]["repository"]["links"]["self"]["href"])) repo_json = json.loads(page, encoding=self.encoding) repo = repo_json["links"]["html"]["href"] # update database yield self._setCurrentRev(nr, revision) # emit the change yield self.master.data.updates.addChange( author=ascii2unicode(author), revision=ascii2unicode(revision), revlink=ascii2unicode(revlink), comments=u"pull-request #%d: %s\n%s" % (nr, title, prlink), when_timestamp=datetime2epoch(updated), branch=self.branch, category=self.category, project=self.project, repository=ascii2unicode(repo), src=u"bitbucket", )
def check(bsdicts): bsdicts = [(bsdict['bsid'], bsdict['complete'], datetime2epoch(bsdict['complete_at']), bsdict['results']) for bsdict in bsdicts] self.assertEqual(sorted(bsdicts), sorted([ (91, 1, 72759, 6), (92, 1, 298297876, 7)]))
def sendBuildFinishedMessage(self, buildid, results=0): self.master.db.builds.finishBuild(buildid=buildid, results=SUCCESS) build = yield self.master.db.builds.getBuild(buildid) self.master.mq.callConsumer(('builds', str(buildid), 'complete'), dict( buildid=buildid, number=build['number'], builderid=build['builderid'], buildrequestid=build['buildrequestid'], workerid=build['workerid'], masterid=build['masterid'], started_at=datetime2epoch(build['started_at']), complete=True, complete_at=datetime2epoch(build['complete_at']), state_string=u'', results=results, ))
def addChange(self, who=None, files=None, comments=None, **kwargs): # deprecated in 0.9.0; will be removed in 1.0.0 log.msg( "WARNING: change source is using deprecated " "self.master.addChange method; this method will disappear in " "Buildbot-1.0.0" ) # handle positional arguments kwargs["who"] = who kwargs["files"] = files kwargs["comments"] = comments def handle_deprec(oldname, newname): if oldname not in kwargs: return old = kwargs.pop(oldname) if old is not None: if kwargs.get(newname) is None: log.msg("WARNING: change source is using deprecated " "addChange parameter '%s'" % oldname) return old raise TypeError("Cannot provide '%s' and '%s' to addChange" % (oldname, newname)) return kwargs.get(newname) kwargs["author"] = handle_deprec("who", "author") kwargs["when_timestamp"] = handle_deprec("when", "when_timestamp") # timestamp must be an epoch timestamp now if isinstance(kwargs.get("when_timestamp"), datetime.datetime): kwargs["when_timestamp"] = datetime2epoch(kwargs["when_timestamp"]) # unicodify stuff for k in ( "comments", "author", "revision", "branch", "category", "revlink", "repository", "codebase", "project", ): if k in kwargs: kwargs[k] = ascii2unicode(kwargs[k]) if kwargs.get("files"): kwargs["files"] = [ascii2unicode(f) for f in kwargs["files"]] if kwargs.get("properties"): kwargs["properties"] = dict((ascii2unicode(k), v) for k, v in iteritems(kwargs["properties"])) # pass the converted call on to the data API changeid = yield self.data.updates.addChange(**kwargs) # and turn that changeid into a change object, since that's what # callers expected (and why this method was deprecated) chdict = yield self.db.changes.getChange(changeid) change = yield changes.Change.fromChdict(self, chdict) defer.returnValue(change)
def test_basic(self): self.setupStep( LogChunksJanitor(logHorizon=timedelta(weeks=1))) self.master.db.logs.deleteOldLogChunks = mock.Mock(return_value=3) self.expectOutcome(result=SUCCESS, state_string=u"deleted 3 logchunks") yield self.runStep() expected_timestamp = datetime2epoch(datetime.datetime(year=2016, month=12, day=25)) self.master.db.logs.deleteOldLogChunks.assert_called_with(expected_timestamp)
def check(res): # when_timestamp is converted from a local time spec, so just # replicate that here when = self.makeTime("2006/04/13 21:55:39") def changeKey(change): """ Let's sort the array of changes by branch, because in P4Source._poll(), changeAdded() is called by iterating over a dictionary of branches""" return change['branch'] self.assertEqual(sorted(self.master.data.updates.changesAdded, key=changeKey), sorted([{ 'author': u'mpatel', 'branch': u'branch_c', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_c_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }, { 'author': u'mpatel', 'branch': u'branch_b', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_b_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }], key=changeKey)) self.assertEqual(self.changesource.last_change, 5) self.assertAllCommandsRan()
def _fixChange(self, change): # TODO: make these mods in the DB API if change: change = change.copy() change['when_timestamp'] = datetime2epoch(change['when_timestamp']) sskey = ('sourcestamps', str(change['sourcestampid'])) change['sourcestamp'] = yield self.master.data.get(sskey) del change['sourcestampid'] defer.returnValue(change)
def check(res): # when_timestamp is converted from 21:55:39 Berlin time to UTC when_berlin = self.makeTime("2006/04/13 21:55:39") when_berlin = when_berlin.replace(tzinfo=dateutil.tz.gettz('Europe/Berlin')) when = datetime2epoch(when_berlin) self.assertEqual([ch['when_timestamp'] for ch in self.master.data.updates.changesAdded], [when, when]) self.assertAllCommandsRan()
def test_completeBuildset(self): yield self.insert_test_getBuildsets_data() yield self.db.buildsets.completeBuildset(bsid=91, results=6) bsdicts = yield self.db.buildsets.getBuildsets() bsdicts = [(bsdict['bsid'], bsdict['complete'], datetime2epoch(bsdict['complete_at']), bsdict['results']) for bsdict in bsdicts] self.assertEqual(sorted(bsdicts), sorted([ (91, 1, self.now, 6), (92, 1, 298297876, 7)]))
def thd(conn): # note that in a read-uncommitted database like SQLite this # transaction does not buy atomicitiy - other database users may # still come across a change without its links, files, properties, # etc. That's OK, since we don't announce the change until it's # all in the database, but beware. transaction = conn.begin() # Trim long comment fields to 1024 characters, but preserve header # and footer with important tags such as Cr-Commit-Position. trimmed_comments = comments if len(trimmed_comments) > 1024: header, footer = trimmed_comments[:506], trimmed_comments[-506:] trimmed_comments = '%s\n...skip...\n%s' % (header, footer) ins = self.db.model.changes.insert() r = conn.execute(ins, dict( author=author, comments=trimmed_comments, is_dir=is_dir, branch=branch, revision=revision, revlink=revlink, when_timestamp=datetime2epoch(when_timestamp), category=category, repository=repository, project=project)) changeid = r.inserted_primary_key[0] if links: ins = self.db.model.change_links.insert() conn.execute(ins, [ dict(changeid=changeid, link=l) for l in links ]) if files: ins = self.db.model.change_files.insert() conn.execute(ins, [ dict(changeid=changeid, filename=f) for f in files ]) if properties: ins = self.db.model.change_properties.insert() conn.execute(ins, [ dict(changeid=changeid, property_name=k, property_value=json.dumps(v)) for k,v in properties.iteritems() ]) transaction.commit() return changeid
def thd(conn): # note that in a read-uncommitted database like SQLite this # transaction does not buy atomicity - other database users may # still come across a change without its files, properties, # etc. That's OK, since we don't announce the change until it's # all in the database, but beware. transaction = conn.begin() r = conn.execute(ch_tbl.insert(), dict( author=author, comments=comments, branch=branch, revision=revision, revlink=revlink, when_timestamp=datetime2epoch(when_timestamp), category=category, repository=repository, codebase=codebase, project=project, sourcestampid=ssid, parent_changeids=parent_changeid)) changeid = r.inserted_primary_key[0] if files: tbl = self.db.model.change_files for f in files: self.checkLength(tbl.c.filename, f) conn.execute(tbl.insert(), [ dict(changeid=changeid, filename=f) for f in files ]) if properties: tbl = self.db.model.change_properties inserts = [ dict(changeid=changeid, property_name=k, property_value=json.dumps(v)) for k, v in iteritems(properties) ] for i in inserts: self.checkLength(tbl.c.property_name, i['property_name']) self.checkLength(tbl.c.property_value, i['property_value']) conn.execute(tbl.insert(), inserts) if uid: ins = self.db.model.change_users.insert() conn.execute(ins, dict(changeid=changeid, uid=uid)) transaction.commit() return changeid
def db2data(self, bsdict): if not bsdict: defer.returnValue(None) buildset = bsdict.copy() # gather the actual sourcestamps, in parallel sourcestamps = [] @defer.inlineCallbacks def getSs(ssid): ss = yield self.master.data.get(('sourcestamps', str(ssid))) sourcestamps.append(ss) yield defer.DeferredList([getSs(id) for id in buildset['sourcestamps']], fireOnOneErrback=True, consumeErrors=True) buildset['sourcestamps'] = sourcestamps # minor modifications buildset['submitted_at'] = datetime2epoch(buildset['submitted_at']) buildset['complete_at'] = datetime2epoch(buildset['complete_at']) defer.returnValue(buildset)
def claimBuildRequests(self, brids, claimed_at=None, _reactor=reactor): for brid in brids: if brid not in self.reqs or brid in self.claims: raise buildrequests.AlreadyClaimedError claimed_at = datetime2epoch(claimed_at) if not claimed_at: claimed_at = _reactor.seconds() # now that we've thrown any necessary exceptions, get started for brid in brids: self.claims[brid] = BuildRequestClaim(brid=brid, objectid=self.MASTER_ID, claimed_at=claimed_at) return defer.succeed(None)
def thd(conn): # note that in a read-uncommitted database like SQLite this # transaction does not buy atomicitiy - other database users may # still come across a change without its links, files, properties, # etc. That's OK, since we don't announce the change until it's # all in the database, but beware. transaction = conn.begin() ins = self.db.model.changes.insert() r = conn.execute(ins, dict( author=author, comments=comments, is_dir=is_dir, branch=branch, revision=revision, revlink=revlink, when_timestamp=datetime2epoch(when_timestamp), category=category, repository=repository, project=project)) changeid = r.inserted_primary_key[0] if links: ins = self.db.model.change_links.insert() conn.execute(ins, [ dict(changeid=changeid, link=l) for l in links ]) if files: ins = self.db.model.change_files.insert() conn.execute(ins, [ dict(changeid=changeid, filename=f) for f in files ]) if properties: ins = self.db.model.change_properties.insert() conn.execute(ins, [ dict(changeid=changeid, property_name=k, property_value=json.dumps(v)) for k,v in properties.iteritems() ]) if uid: ins = self.db.model.change_users.insert() conn.execute(ins, dict(changeid=changeid, uid=uid)) transaction.commit() return changeid
def check(res): # when_timestamp is converted from a local time spec, so just # replicate that here when = self.makeTime("2006/04/13 21:55:39") self.assertEqual(self.master.data.updates.changesAdded, [{ 'author': u'mpatel', 'branch': u'branch_c', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_c_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }, { 'author': u'mpatel', 'branch': u'branch_b', 'category': None, 'codebase': None, 'comments': u'This is a multiline comment with tabs and spaces\n\nA list:\n Item 1\n\tItem 2', 'files': [u'branch_b_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }]) self.assertEquals(self.changesource.last_change, 5) self.assertAllCommandsRan()
def completeBuildRequests(self, brids, results, complete_at=None, _reactor=reactor): if complete_at is not None: complete_at = datetime2epoch(complete_at) else: complete_at = _reactor.seconds() for brid in brids: if brid not in self.reqs or self.reqs[brid].complete == 1: raise buildrequests.NotClaimedError for brid in brids: self.reqs[brid].complete = 1 self.reqs[brid].results = results self.reqs[brid].complete_at = complete_at
def check(res): # when_timestamp is converted from a local time spec, so just # replicate that here when = self.makeTime("2006/04/13 21:55:39") self.assertEqual(self.master.data.updates.changesAdded, [{ 'author': u'mpatel', 'branch': u'branch_c', 'category': None, 'codebase': None, 'comments': u'Change 4 by mpatel@testclient on 2006/04/13 21:55:39\n\n\tshort desc truncated because this is a long description.\n', 'files': [u'branch_c_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }, { 'author': u'mpatel', 'branch': u'branch_b', 'category': None, 'codebase': None, 'comments': u'Change 4 by mpatel@testclient on 2006/04/13 21:55:39\n\n\tshort desc truncated because this is a long description.\n', 'files': [u'branch_b_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }]) self.assertEquals(self.changesource.last_change, 5) self.assertAllCommandsRan()
def completeBuildset(self, bsid, results, complete_at=None, _reactor=reactor): if complete_at is not None: complete_at = datetime2epoch(complete_at) else: complete_at = _reactor.seconds() def thd(conn): tbl = self.db.model.buildsets q = tbl.update(whereclause=((tbl.c.id == bsid) & ((tbl.c.complete == NULL) | (tbl.c.complete != 1)))) res = conn.execute(q, complete=1, results=results, complete_at=complete_at) if res.rowcount != 1: raise KeyError return self.db.pool.do(thd)
def submitChanges(self, changes, request, src): for chdict in changes: when_timestamp = chdict.get('when_timestamp') if isinstance(when_timestamp, datetime): chdict['when_timestamp'] = datetime2epoch(when_timestamp) # unicodify stuff for k in ('comments', 'author', 'revision', 'branch', 'category', 'revlink', 'repository', 'codebase', 'project'): if k in chdict: chdict[k] = bytes2unicode(chdict[k]) if chdict.get('files'): chdict['files'] = [bytes2unicode(f) for f in chdict['files']] if chdict.get('properties'): chdict['properties'] = dict((bytes2unicode(k), v) for k, v in chdict['properties'].items()) chid = yield self.master.data.updates.addChange(src=bytes2unicode(src), **chdict) log.msg("injected change %s" % chid)
def _processChanges(self, page): result = json.loads(page, encoding=self.encoding) for pr in result['values']: branch = pr['source']['branch']['name'] nr = int(pr['id']) # Note that this is a short hash. The full length hash can be accessed via the # commit api resource but we want to avoid requesting multiple pages as long as # we are not sure that the pull request is new or updated. revision = pr['source']['commit']['hash'] # check branch if not self.branch or branch in self.branch: current = yield self._getCurrentRev(nr) # compare _short_ hashes to check if the PR has been updated if not current or current[0:12] != revision[0:12]: # parse pull request api page (required for the filter) page = yield client.getPage(str(pr['links']['self']['href'])) pr_json = json.loads(page, encoding=self.encoding) # filter pull requests by user function if not self.pullrequest_filter(pr_json): log.msg('pull request does not match filter') continue # access additional information author = pr['author']['display_name'] prlink = pr['links']['html']['href'] # Get time updated time. Note that the timezone offset is # ignored. if self.useTimestamps: updated = datetime.strptime( pr['updated_on'].split('.')[0], '%Y-%m-%dT%H:%M:%S') else: updated = epoch2datetime(reactor.seconds()) title = pr['title'] # parse commit api page page = yield client.getPage(str(pr['source']['commit']['links']['self']['href'])) commit_json = json.loads(page, encoding=self.encoding) # use the full-length hash from now on revision = commit_json['hash'] revlink = commit_json['links']['html']['href'] # parse repo api page page = yield client.getPage(str(pr['source']['repository']['links']['self']['href'])) repo_json = json.loads(page, encoding=self.encoding) repo = repo_json['links']['html']['href'] # update database yield self._setCurrentRev(nr, revision) # emit the change yield self.master.data.updates.addChange( author=bytes2unicode(author), revision=bytes2unicode(revision), revlink=bytes2unicode(revlink), comments=u'pull-request #%d: %s\n%s' % ( nr, title, prlink), when_timestamp=datetime2epoch(updated), branch=bytes2unicode(branch), category=self.category, project=self.project, repository=bytes2unicode(repo), src=u'bitbucket', )
def test_datetime2epoch(self): dt = datetime.datetime(1970, 1, 1, 0, 0, 0, tzinfo=util.UTC) self.assertEqual(util.datetime2epoch(dt), 0) dt = datetime.datetime(2011, 3, 13, 7, 6, 40, tzinfo=util.UTC) self.assertEqual(util.datetime2epoch(dt), 1300000000)
def addBuildset(self, sourcestamps, reason, properties, builderids, waited_for, external_idstring=None, submitted_at=None, parent_buildid=None, parent_relationship=None, _reactor=reactor): if submitted_at: submitted_at = datetime2epoch(submitted_at) else: submitted_at = _reactor.seconds() # convert to sourcestamp IDs first, as necessary def toSsid(sourcestamp): if isinstance(sourcestamp, integer_types): return defer.succeed(sourcestamp) else: ssConnector = self.master.db.sourcestamps return ssConnector.findSourceStampId(**sourcestamp) sourcestamps = yield defer.DeferredList( [toSsid(ss) for ss in sourcestamps], fireOnOneErrback=True, consumeErrors=True) sourcestampids = [r[1] for r in sourcestamps] def thd(conn): buildsets_tbl = self.db.model.buildsets self.checkLength(buildsets_tbl.c.reason, reason) self.checkLength(buildsets_tbl.c.external_idstring, external_idstring) transaction = conn.begin() # insert the buildset itself r = conn.execute( buildsets_tbl.insert(), dict(submitted_at=submitted_at, reason=reason, complete=0, complete_at=None, results=-1, external_idstring=external_idstring, parent_buildid=parent_buildid, parent_relationship=parent_relationship)) bsid = r.inserted_primary_key[0] # add any properties if properties: bs_props_tbl = self.db.model.buildset_properties inserts = [ dict(buildsetid=bsid, property_name=k, property_value=json.dumps([v, s])) for k, (v, s) in iteritems(properties) ] for i in inserts: self.checkLength(bs_props_tbl.c.property_name, i['property_name']) conn.execute(bs_props_tbl.insert(), inserts) # add sourcestamp ids r = conn.execute(self.db.model.buildset_sourcestamps.insert(), [ dict(buildsetid=bsid, sourcestampid=ssid) for ssid in sourcestampids ]) # and finish with a build request for each builder. Note that # sqlalchemy and the Python DBAPI do not provide a way to recover # inserted IDs from a multi-row insert, so this is done one row at # a time. brids = {} br_tbl = self.db.model.buildrequests ins = br_tbl.insert() for builderid in builderids: r = conn.execute( ins, dict(buildsetid=bsid, builderid=builderid, priority=0, claimed_at=0, claimed_by_name=None, claimed_by_incarnation=None, complete=0, results=-1, submitted_at=submitted_at, complete_at=None, waited_for=1 if waited_for else 0)) brids[builderid] = r.inserted_primary_key[0] transaction.commit() return (bsid, brids) bsid, brids = yield self.db.pool.do(thd) # Seed the buildset property cache. self.getBuildsetProperties.cache.put(bsid, BsProps(properties)) defer.returnValue((bsid, brids))
def run(self): older_than_timestamp = datetime2epoch(now() - self.logHorizon) deleted = yield self.master.db.logs.deleteOldLogChunks( older_than_timestamp) self.descriptionDone = ["deleted", str(deleted), "logchunks"] defer.returnValue(SUCCESS)
def _processChanges(self, github_result): for pr in github_result: # Track PRs for specified branches base_branch = pr['base']['ref'] prnumber = pr['number'] revision = pr['head']['sha'] # Check to see if the branch is set or matches if self.branches is not None and base_branch not in self.branches: continue if (self.pullrequest_filter is not None and not self.pullrequest_filter(pr)): continue current = yield self._getCurrentRev(prnumber) if not current or current[0:12] != revision[0:12]: # Access title, repo, html link, and comments pr = yield self._getPullInformation(prnumber) title = pr['title'] if self.magic_link: branch = 'refs/pull/{:d}/merge'.format(prnumber) repo = pr['base']['repo'][self.repository_type] else: branch = pr['head']['ref'] repo = pr['head']['repo'][self.repository_type] revlink = pr['html_url'] comments = pr['body'] updated = datetime.strptime(pr['updated_at'], '%Y-%m-%dT%H:%M:%SZ') # update database yield self._setCurrentRev(prnumber, revision) author = pr['user']['login'] project = pr['base']['repo']['full_name'] commits = pr['commits'] dl = defer.DeferredList( [self._getFiles(prnumber), self._getEmail(author)], consumeErrors=True) results = yield dl failures = [r[1] for r in results if not r[0]] if failures: for failure in failures: log.error("while processing changes for " "Pullrequest {} revision {}".format( prnumber, revision)) # Fail on the first error! failures[0].raiseException() [files, email] = [r[1] for r in results] if email is not None and email != "null": author += " <" + str(email) + ">" properties = self.extractProperties(pr) # emit the change yield self.master.data.updates.addChange( author=bytes2unicode(author), revision=bytes2unicode(revision), revlink=bytes2unicode(revlink), comments='GitHub Pull Request #{0} ({1} commit{2})\n{3}\n{4}' .format(prnumber, commits, 's' if commits > 0 else '', title, comments), when_timestamp=datetime2epoch(updated), branch=bytes2unicode(branch), category=self.category, project=project, repository=bytes2unicode(repo), files=files, properties=properties, src='git')
def thd(conn): # note that in a read-uncommitted database like SQLite this # transaction does not buy atomicitiy - other database users may # still come across a change without its files, properties, # etc. That's OK, since we don't announce the change until it's # all in the database, but beware. transaction = conn.begin() ch_tbl = self.db.model.changes self.check_length(ch_tbl.c.author, author) self.check_length(ch_tbl.c.comments, comments) self.check_length(ch_tbl.c.branch, branch) self.check_length(ch_tbl.c.revision, revision) self.check_length(ch_tbl.c.revlink, revlink) self.check_length(ch_tbl.c.category, category) self.check_length(ch_tbl.c.repository, repository) self.check_length(ch_tbl.c.project, project) r = conn.execute( ch_tbl.insert(), dict(author=author, comments=comments, is_dir=is_dir, branch=branch, revision=revision, revlink=revlink, when_timestamp=datetime2epoch(when_timestamp), category=category, repository=repository, codebase=codebase, project=project)) changeid = r.inserted_primary_key[0] if files: tbl = self.db.model.change_files for f in files: self.check_length(tbl.c.filename, f) conn.execute( tbl.insert(), [dict(changeid=changeid, filename=f) for f in files]) if properties: tbl = self.db.model.change_properties inserts = [ dict(changeid=changeid, property_name=k, property_value=json.dumps(v)) for k, v in properties.iteritems() ] for i in inserts: self.check_length(tbl.c.property_name, i['property_name']) self.check_length(tbl.c.property_value, i['property_value']) conn.execute(tbl.insert(), inserts) if uid: ins = self.db.model.change_users.insert() conn.execute(ins, dict(changeid=changeid, uid=uid)) transaction.commit() return changeid
def test_poll_split_file(self): """Make sure split file works on branch only changes""" yield self.attachChangeSource( P4Source(p4port=None, p4user=None, p4base='//depot/myproject/', split_file=get_simple_split)) self.expect_commands( ExpectMasterShell(['p4', 'changes', '//depot/myproject/...@51,#head']) .stdout(third_p4changes), ) self.add_p4_describe_result(5, p4change[5]) self.changesource.last_change = 50 yield self.changesource.poll() # when_timestamp is converted from a local time spec, so just # replicate that here when = self.makeTime("2006/04/13 21:55:39") def changeKey(change): """ Let's sort the array of changes by branch, because in P4Source._poll(), changeAdded() is called by iterating over a dictionary of branches""" return change['branch'] self.assertEqual(sorted(self.master.data.updates.changesAdded, key=changeKey), sorted([{ 'author': 'mpatel', 'committer': None, 'branch': 'branch_c', 'category': None, 'codebase': None, 'comments': 'This is a multiline comment with tabs and spaces\n\nA list:\n ' 'Item 1\n\tItem 2', 'files': ['branch_c_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }, { 'author': 'mpatel', 'committer': None, 'branch': 'branch_b', 'category': None, 'codebase': None, 'comments': 'This is a multiline comment with tabs and spaces\n\nA list:\n ' 'Item 1\n\tItem 2', 'files': ['branch_b_file'], 'project': '', 'properties': {}, 'repository': '', 'revision': '5', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when), }], key=changeKey)) self.assertEqual(self.changesource.last_change, 5) self.assert_all_commands_ran()
def _poll(self): if self.use_tickets: self._ticket_login_counter -= 1 if self._ticket_login_counter <= 0: # Re-acquire the ticket and reset the counter. log.msg("P4Poller: (re)acquiring P4 ticket for %s..." % self.p4base) protocol = TicketLoginProtocol(self.p4passwd + "\n", self.p4base) self._acquireTicket(protocol) yield protocol.deferred self._ticket_passwd = self._parseTicketPassword( protocol.stdout) self._ticket_login_counter = max( self.ticket_login_interval / self.pollInterval, 1) if debug_logging: log.msg("P4Poller: got ticket password: %s" % self._ticket_passwd) log.msg("P4Poller: next ticket acquisition in %d polls" % self._ticket_login_counter) args = [] if self.p4port: args.extend(['-p', self.p4port]) if self.p4user: args.extend(['-u', self.p4user]) if self.p4passwd: args.extend(['-P', self._getPasswd()]) args.extend(['changes']) if self.last_change is not None: args.extend( ['%s...@%d,#head' % (self.p4base, self.last_change + 1)]) else: args.extend(['-m', '1', '%s...' % (self.p4base, )]) result = yield self._get_process_output(args) # decode the result from its designated encoding try: result = bytes2unicode(result, self.encoding) except UnicodeError as ex: log.msg(u"Warning: cannot fully decode {} in {}".format( repr(result), self.encoding)) result = bytes2unicode(result, encoding=self.encoding, errors="replace") last_change = self.last_change changelists = [] for line in result.split('\n'): line = line.strip() if not line: continue m = self.changes_line_re.match(line) if not m: raise P4PollerError("Unexpected 'p4 changes' output: %r" % result) num = int(m.group('num')) if last_change is None: # first time through, the poller just gets a "baseline" for where to # start on the next poll log.msg('P4Poller: starting at change %d' % num) self.last_change = num return changelists.append(num) changelists.reverse() # oldest first # Retrieve each sequentially. for num in changelists: args = [] if self.p4port: args.extend(['-p', self.p4port]) if self.p4user: args.extend(['-u', self.p4user]) if self.p4passwd: args.extend(['-P', self._getPasswd()]) args.extend(['describe', '-s', str(num)]) result = yield self._get_process_output(args) # decode the result from its designated encoding try: result = bytes2unicode(result, self.encoding) except UnicodeError as ex: log.msg( "P4Poller: couldn't decode changelist description: %s" % ex.encoding) log.msg("P4Poller: in object: %s" % ex.object) log.err("P4Poller: poll failed on %s, %s" % (self.p4port, self.p4base)) raise lines = result.split('\n') # SF#1555985: Wade Brainerd reports a stray ^M at the end of the date # field. The rstrip() is intended to remove that. lines[0] = lines[0].rstrip() m = self.describe_header_re.match(lines[0]) if not m: raise P4PollerError("Unexpected 'p4 describe -s' result: %r" % result) who = m.group('who') when = datetime.datetime.strptime(m.group('when'), self.datefmt) if self.server_tz: # Convert from the server's timezone to the local timezone. when = when.replace(tzinfo=self.server_tz) when = util.datetime2epoch(when) comment_lines = [] lines.pop(0) # describe header lines.pop(0) # blank line while not lines[0].startswith('Affected files'): if lines[0].startswith('\t'): # comment is indented by one tab comment_lines.append(lines.pop(0)[1:]) else: lines.pop(0) # discard non comment line comments = '\n'.join(comment_lines) lines.pop(0) # affected files branch_files = {} # dict for branch mapped to file(s) while lines: line = lines.pop(0).strip() if not line: continue m = self.file_re.match(line) if not m: raise P4PollerError("Invalid file line: %r" % line) path = m.group('path') if path.startswith(self.p4base): branch, file = self.split_file(path[len(self.p4base):]) if (branch is None and file is None): continue if branch in branch_files: branch_files[branch].append(file) else: branch_files[branch] = [file] for branch in branch_files: yield self.master.data.updates.addChange( author=who, files=branch_files[branch], comments=comments, revision=text_type(num), when_timestamp=when, branch=branch, project=self.project) self.last_change = num
def run(self): older_than_timestamp = datetime2epoch(now() - self.build_data_horizon) deleted = yield self.master.db.build_data.deleteOldBuildData(older_than_timestamp) self.descriptionDone = ["deleted", str(deleted), "build data key-value pairs"] return SUCCESS
def _toJson(self, obj): if isinstance(obj, datetime.datetime): return datetime2epoch(obj)
def populate_build(db, build_count, builders_list, projects, user_names, verbose=True): """ :param db: a handler to the DBConnection object :param build_count: an integer value with number of new builds :param builders_list: a list of builders. The builder is a BuilderConfig object :param projects: a list of a ProjectConfig objects :param user_names: a list of an usernames (identifier) from the database :param verbose: a boolean value indicate to print all information to std output """ from progress_bar import InitBar def handler(result, counter, *args): result[counter] += 1 progress_bar = InitBar(size=build_count, stream=sys.stdout) if verbose: print("Starting creating builds") res = { 'created': 0, 'skipped': 0, } for number in range(build_count): builder = random.choice(builders_list) codebases = random.choice(projects[builder.project].codebases) codebase = random.choice(codebases.keys()) repository = codebases[codebase] submitted_at = datetime2epoch( datetime.datetime.now() + datetime.timedelta( seconds=random.randint(-3 * 60 * 60, -3 * 60 * 60))) complete_at = submitted_at + random.randint(60 * 60, 3 * 60 * 60) build = { 'branch': repository['branch'], 'revision': "%032x" % random.getrandbits(160), # Random sha-1 hash 'repository': repository['repository'], 'codebase': codebase, 'project': builder.project, 'reason': 'A build was forced by {username} {username}@localhost'.format( username=random.choice(user_names)), 'submitted_at': submitted_at, 'complete_at': complete_at, 'buildername': builder.name, 'slavepool': None, 'number': number, 'slavename': random.choice(builder.slavenames), 'results': random.choice(COMPLETED_RESULTS), } promise = db.builds.createFullBuildObject(**build) promise.addCallback(lambda *args: handler(res, 'created')) promise.addErrback(lambda *args: handler(res, 'skipped')) yield promise if verbose: progress_bar(number + 1) if verbose: print() print("Created %d new builds, %d skipped" % (res['created'], res['skipped']))
def do_test_poll_successful(self, **kwargs): encoding = kwargs.get('encoding', 'utf8') yield self.attachChangeSource( P4Source(p4port=None, p4user=None, p4base='//depot/myproject/', split_file=lambda x: x.split('/', 1), **kwargs)) self.expect_commands( ExpectMasterShell(['p4', 'changes', '-m', '1', '//depot/myproject/...']) .stdout(first_p4changes), ExpectMasterShell(['p4', 'changes', '//depot/myproject/...@2,#head']) .stdout(second_p4changes), ) encoded_p4change = p4change.copy() encoded_p4change[3] = encoded_p4change[3].encode(encoding) self.add_p4_describe_result(2, encoded_p4change[2]) self.add_p4_describe_result(3, encoded_p4change[3]) # The first time, it just learns the change to start at. self.assertTrue(self.changesource.last_change is None) yield self.changesource.poll() self.assertEqual(self.master.data.updates.changesAdded, []) self.assertEqual(self.changesource.last_change, 1) # Subsequent times, it returns Change objects for new changes. yield self.changesource.poll() # when_timestamp is converted from a local time spec, so just # replicate that here when1 = self.makeTime("2006/04/13 21:46:23") when2 = self.makeTime("2006/04/13 21:51:39") # these two can happen in either order, since they're from the same # perforce change. changesAdded = self.master.data.updates.changesAdded if changesAdded[1]['branch'] == 'branch_c': changesAdded[1:] = reversed(changesAdded[1:]) self.assertEqual(self.master.data.updates.changesAdded, [{ 'author': 'slamb', 'committer': None, 'branch': 'trunk', 'category': None, 'codebase': None, 'comments': 'creation', 'files': ['whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '2', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when1), }, { 'author': 'bob', 'committer': None, 'branch': 'branch_b', 'category': None, 'codebase': None, 'comments': 'short desc truncated because this is a long description.\n' 'ASDF-GUI-P3-\u2018Upgrade Icon\u2019 disappears sometimes.', 'files': ['branch_b_file', 'whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '3', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when2), }, { 'author': 'bob', 'committer': None, 'branch': 'branch_c', 'category': None, 'codebase': None, 'comments': 'short desc truncated because this is a long description.\n' 'ASDF-GUI-P3-\u2018Upgrade Icon\u2019 disappears sometimes.', 'files': ['whatbranch'], 'project': '', 'properties': {}, 'repository': '', 'revision': '3', 'revlink': '', 'src': None, 'when_timestamp': datetime2epoch(when2), }]) self.assert_all_commands_ran()