def test_sort_stack_topological_cycle(): nodes = {1, 2, 3, 4} edges = { Edge(child=1, parent=2), Edge(child=2, parent=3), Edge(child=3, parent=1), Edge(child=1, parent=4), } with pytest.raises(ValueError): sort_stack_topological(nodes, edges)
def test_sort_stack_topological_complex(): nodes = set('PHID-DREV-{}'.format(i) for i in range(10)) edges = { Edge(child='PHID-DREV-1', parent='PHID-DREV-0'), Edge(child='PHID-DREV-2', parent='PHID-DREV-0'), Edge(child='PHID-DREV-2', parent='PHID-DREV-3'), Edge(child='PHID-DREV-4', parent='PHID-DREV-2'), Edge(child='PHID-DREV-5', parent='PHID-DREV-4'), Edge(child='PHID-DREV-6', parent='PHID-DREV-1'), Edge(child='PHID-DREV-7', parent='PHID-DREV-6'), Edge(child='PHID-DREV-7', parent='PHID-DREV-5'), Edge(child='PHID-DREV-9', parent='PHID-DREV-7'), Edge(child='PHID-DREV-8', parent='PHID-DREV-9'), } order = sort_stack_topological(nodes, edges, key=lambda x: int(x.split('-')[2])) assert order == [ 'PHID-DREV-0', 'PHID-DREV-1', 'PHID-DREV-3', 'PHID-DREV-2', 'PHID-DREV-4', 'PHID-DREV-5', 'PHID-DREV-6', 'PHID-DREV-7', 'PHID-DREV-9', 'PHID-DREV-8', ]
def test_sort_stack_topological_linear(): revs = ["PHID-DREV-{}".format(i) for i in range(10)] nodes = {phid for phid in revs} edges = {Edge(child=revs[i], parent=revs[i - 1]) for i in range(1, 10)} order = sort_stack_topological(nodes, edges) assert order == revs
def test_sort_stack_topological_complex(): nodes = set("PHID-DREV-{}".format(i) for i in range(10)) edges = { Edge(child="PHID-DREV-1", parent="PHID-DREV-0"), Edge(child="PHID-DREV-2", parent="PHID-DREV-0"), Edge(child="PHID-DREV-2", parent="PHID-DREV-3"), Edge(child="PHID-DREV-4", parent="PHID-DREV-2"), Edge(child="PHID-DREV-5", parent="PHID-DREV-4"), Edge(child="PHID-DREV-6", parent="PHID-DREV-1"), Edge(child="PHID-DREV-7", parent="PHID-DREV-6"), Edge(child="PHID-DREV-7", parent="PHID-DREV-5"), Edge(child="PHID-DREV-9", parent="PHID-DREV-7"), Edge(child="PHID-DREV-8", parent="PHID-DREV-9"), } order = sort_stack_topological(nodes, edges, key=lambda x: int(x.split("-")[2])) assert order == [ "PHID-DREV-0", "PHID-DREV-1", "PHID-DREV-3", "PHID-DREV-2", "PHID-DREV-4", "PHID-DREV-5", "PHID-DREV-6", "PHID-DREV-7", "PHID-DREV-9", "PHID-DREV-8", ]
def test_draw_stack_graph_complex(): nodes = set('PHID-DREV-{}'.format(i) for i in range(10)) edges = { Edge(child='PHID-DREV-1', parent='PHID-DREV-0'), Edge(child='PHID-DREV-2', parent='PHID-DREV-0'), Edge(child='PHID-DREV-2', parent='PHID-DREV-3'), Edge(child='PHID-DREV-4', parent='PHID-DREV-2'), Edge(child='PHID-DREV-5', parent='PHID-DREV-4'), Edge(child='PHID-DREV-6', parent='PHID-DREV-1'), Edge(child='PHID-DREV-7', parent='PHID-DREV-6'), Edge(child='PHID-DREV-7', parent='PHID-DREV-5'), Edge(child='PHID-DREV-9', parent='PHID-DREV-7'), Edge(child='PHID-DREV-8', parent='PHID-DREV-9'), } order = sort_stack_topological(nodes, edges, key=lambda x: int(x.split('-')[2])) width, rows = draw_stack_graph(nodes, edges, order) assert width == 3 assert rows == [{ 'above': [0, 1], 'below': [], 'node': 'PHID-DREV-0', 'other': [], 'pos': 0 }, { 'above': [0], 'below': [0], 'node': 'PHID-DREV-1', 'other': [1], 'pos': 0 }, { 'above': [2], 'below': [], 'node': 'PHID-DREV-3', 'other': [0, 1], 'pos': 2 }, { 'above': [1], 'below': [1, 2], 'node': 'PHID-DREV-2', 'other': [0], 'pos': 1 }, { 'above': [1], 'below': [1], 'node': 'PHID-DREV-4', 'other': [0], 'pos': 1 }, { 'above': [1], 'below': [1], 'node': 'PHID-DREV-5', 'other': [0], 'pos': 1 }, { 'above': [0], 'below': [0], 'node': 'PHID-DREV-6', 'other': [1], 'pos': 0 }, { 'above': [0], 'below': [0, 1], 'node': 'PHID-DREV-7', 'other': [], 'pos': 0 }, { 'above': [0], 'below': [0], 'node': 'PHID-DREV-9', 'other': [], 'pos': 0 }, { 'above': [], 'below': [0], 'node': 'PHID-DREV-8', 'other': [], 'pos': 0 }]
def test_sort_stack_topological_favors_minimum(): nodes = set(range(10)) edges = {Edge(child=0, parent=i) for i in range(1, 10)} order = sort_stack_topological(nodes, edges) assert order == list(range(1, 10)) + [0]
def test_sort_stack_topological_single_node(): order = sort_stack_topological({"PHID-DREV-0"}, set()) assert len(order) == 1 assert order[0] == "PHID-DREV-0"
def test_draw_stack_graph_complex(): nodes = set("PHID-DREV-{}".format(i) for i in range(10)) edges = { Edge(child="PHID-DREV-1", parent="PHID-DREV-0"), Edge(child="PHID-DREV-2", parent="PHID-DREV-0"), Edge(child="PHID-DREV-2", parent="PHID-DREV-3"), Edge(child="PHID-DREV-4", parent="PHID-DREV-2"), Edge(child="PHID-DREV-5", parent="PHID-DREV-4"), Edge(child="PHID-DREV-6", parent="PHID-DREV-1"), Edge(child="PHID-DREV-7", parent="PHID-DREV-6"), Edge(child="PHID-DREV-7", parent="PHID-DREV-5"), Edge(child="PHID-DREV-9", parent="PHID-DREV-7"), Edge(child="PHID-DREV-8", parent="PHID-DREV-9"), } order = sort_stack_topological(nodes, edges, key=lambda x: int(x.split("-")[2])) width, rows = draw_stack_graph(nodes, edges, order) assert width == 3 assert rows == [ { "above": [0, 1], "below": [], "node": "PHID-DREV-0", "other": [], "pos": 0 }, { "above": [0], "below": [0], "node": "PHID-DREV-1", "other": [1], "pos": 0 }, { "above": [2], "below": [], "node": "PHID-DREV-3", "other": [0, 1], "pos": 2 }, { "above": [1], "below": [1, 2], "node": "PHID-DREV-2", "other": [0], "pos": 1 }, { "above": [1], "below": [1], "node": "PHID-DREV-4", "other": [0], "pos": 1 }, { "above": [1], "below": [1], "node": "PHID-DREV-5", "other": [0], "pos": 1 }, { "above": [0], "below": [0], "node": "PHID-DREV-6", "other": [1], "pos": 0 }, { "above": [0], "below": [0, 1], "node": "PHID-DREV-7", "other": [], "pos": 0 }, { "above": [0], "below": [0], "node": "PHID-DREV-9", "other": [], "pos": 0 }, { "above": [], "below": [0], "node": "PHID-DREV-8", "other": [], "pos": 0 }, ]
def revision(revision_id): api = LandoAPI( current_app.config["LANDO_API_URL"], auth0_access_token=session.get("access_token"), phabricator_api_token=get_phabricator_api_token(), ) form = TransplantRequestForm() sec_approval_form = SecApprovalRequestForm() errors = [] if form.is_submitted(): if not is_user_authenticated(): errors.append("You must be logged in to request a landing") elif not form.validate(): for _, field_errors in form.errors.items(): errors.extend(field_errors) else: try: api.request( "POST", "transplants", require_auth0=True, json={ "landing_path": json.loads(form.landing_path.data), "confirmation_token": form.confirmation_token.data, }, ) # We don't actually need any of the data from the # the submission. As long as an exception wasn't # raised we're successful. return redirect(url_for("revisions.revision", revision_id=revision_id)) except LandoAPIError as e: if not e.detail: raise errors.append(e.detail) # Request the entire stack. try: stack = api.request("GET", "stacks/D{}".format(revision_id)) except LandoAPIError as e: if e.status_code == 404: raise RevisionNotFound(revision_id) else: raise # Build a mapping from phid to revision and identify # the data for the revision used to load this page. revision = None revisions = {} for r in stack["revisions"]: revisions[r["phid"]] = r if r["id"] == "D{}".format(revision_id): revision = r["phid"] # Build a mapping from phid to repository. repositories = {} for r in stack["repositories"]: repositories[r["phid"]] = r # Request all previous transplants for the stack. transplants = api.request( "GET", "transplants", params={"stack_revision_id": "D{}".format(revision_id)} ) # The revision may appear in many `landable_paths`` if it has # multiple children, or any of its landable descendents have # multiple children. That being said, there should only be a # single unique path up to this revision, so find the first # it appears in. The revisions up to the target one in this # path form the landable series. # # We also form a set of all the revisions that are landable # so we can present selection for what to land. series = None landable = set() for p in stack["landable_paths"]: for phid in p: landable.add(phid) try: series = p[: p.index(revision) + 1] except ValueError: pass dryrun = None target_repo = None if series and is_user_authenticated(): landing_path = [ { "revision_id": revisions[phid]["id"], "diff_id": revisions[phid]["diff"]["id"], } for phid in series ] form.landing_path.data = json.dumps(landing_path) dryrun = api.request( "POST", "transplants/dryrun", require_auth0=True, json={"landing_path": landing_path}, ) form.confirmation_token.data = dryrun.get("confirmation_token") series = list(reversed(series)) target_repo = repositories.get(revisions[series[0]]["repo_phid"]) phids = set(revisions.keys()) edges = set(Edge(child=e[0], parent=e[1]) for e in stack["edges"]) order = sort_stack_topological( phids, edges, key=lambda x: int(revisions[x]["id"][1:]) ) drawing_width, drawing_rows = draw_stack_graph(phids, edges, order) annotate_sec_approval_workflow_info(revisions) # Are we showing the "sec-approval request submitted" dialog? # If we are then fill in its values. submitted_revision = request.args.get("show_approval_success") submitted_rev_url = None if submitted_revision: for rev in revisions.values(): if rev["id"] == submitted_revision: submitted_rev_url = rev["url"] break return render_template( "stack/stack.html", revision_id="D{}".format(revision_id), series=series, landable=landable, dryrun=dryrun, stack=stack, rows=list(zip(reversed(order), reversed(drawing_rows))), drawing_width=drawing_width, transplants=transplants, revisions=revisions, revision_phid=revision, sec_approval_form=sec_approval_form, submitted_rev_url=submitted_rev_url, target_repo=target_repo, errors=errors, form=form, )
def revision(revision_id): api = LandoAPI(current_app.config['LANDO_API_URL'], auth0_access_token=session.get('access_token'), phabricator_api_token=get_phabricator_api_token()) form = TransplantRequestForm() errors = [] if form.is_submitted(): if not is_user_authenticated(): errors.append('You must be logged in to request a landing') elif not form.validate(): for _, field_errors in form.errors.items(): errors.extend(field_errors) else: try: api.request('POST', 'transplants', require_auth0=True, json={ 'landing_path': json.loads(form.landing_path.data), 'confirmation_token': form.confirmation_token.data, }) # We don't actually need any of the data from the # the submission. As long as an exception wasn't # raised we're successful. return redirect( url_for('revisions.revision', revision_id=revision_id)) except LandoAPIError as e: if not e.detail: raise errors.append(e.detail) # Request the entire stack. try: stack = api.request('GET', 'stacks/D{}'.format(revision_id)) except LandoAPIError as e: if e.status_code == 404: raise RevisionNotFound(revision_id) else: raise # Build a mapping from phid to revision and identify # the data for the revision used to load this page. revision = None revisions = {} for r in stack['revisions']: revisions[r['phid']] = r if r['id'] == 'D{}'.format(revision_id): revision = r['phid'] # Build a mapping from phid to repository. repositories = {} for r in stack['repositories']: repositories[r['phid']] = r # Request all previous transplants for the stack. transplants = api.request( 'GET', 'transplants', params={'stack_revision_id': 'D{}'.format(revision_id)}) # The revision may appear in many `landable_paths`` if it has # multiple children, or any of its landable descendents have # multiple children. That being said, there should only be a # single unique path up to this revision, so find the first # it appears in. The revisions up to the target one in this # path form the landable series. # # We also form a set of all the revisions that are landable # so we can present selection for what to land. series = None landable = set() for p in stack['landable_paths']: for phid in p: landable.add(phid) try: series = p[:p.index(revision) + 1] except ValueError: pass dryrun = None target_repo = None if series and is_user_authenticated(): landing_path = [{ 'revision_id': revisions[phid]['id'], 'diff_id': revisions[phid]['diff']['id'], } for phid in series] form.landing_path.data = json.dumps(landing_path) dryrun = api.request('POST', 'transplants/dryrun', require_auth0=True, json={'landing_path': landing_path}) form.confirmation_token.data = dryrun.get('confirmation_token') series = list(reversed(series)) target_repo = repositories.get(revisions[series[0]]['repo_phid']) phids = set(revisions.keys()) edges = set(Edge(child=e[0], parent=e[1]) for e in stack['edges']) order = sort_stack_topological(phids, edges, key=lambda x: int(revisions[x]['id'][1:])) drawing_width, drawing_rows = draw_stack_graph(phids, edges, order) return render_template( 'stack/stack.html', revision_id='D{}'.format(revision_id), series=series, landable=landable, dryrun=dryrun, stack=stack, rows=list(zip(reversed(order), reversed(drawing_rows))), drawing_width=drawing_width, transplants=transplants, revisions=revisions, revision_phid=revision, target_repo=target_repo, errors=errors, form=form, )