def _basic_trace_frames(self):
     return [
         TraceFrame(
             id=1,
             kind=TraceKind.PRECONDITION,
             caller="call1",
             caller_port="root",
             callee="call2",
             callee_port="param0",
             callee_location=SourceLocation(1, 1),
             filename="file.py",
             run_id=1,
         ),
         TraceFrame(
             id=2,
             kind=TraceKind.PRECONDITION,
             caller="call2",
             caller_port="param0",
             callee="leaf",
             callee_port="sink",
             callee_location=SourceLocation(1, 2),
             filename="file.py",
             run_id=1,
         ),
     ]
    def testTraceCursorLocation(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        trace_frames = [
            TraceFrame(
                id=1,
                kind=TraceKind.POSTCONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="source",
                callee_location=SourceLocation(1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=2,
                kind=TraceKind.PRECONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="sink",
                callee_location=SourceLocation(1, 2),
                filename="file.py",
                run_id=1,
            ),
        ]
        assocs = [
            IssueInstanceTraceFrameAssoc(trace_frame_id=1, issue_instance_id=1),
            IssueInstanceTraceFrameAssoc(trace_frame_id=2, issue_instance_id=1),
            TraceFrameLeafAssoc(trace_frame_id=1, leaf_id=1),
            TraceFrameLeafAssoc(trace_frame_id=2, leaf_id=1),
        ]
        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            self._add_to_session(session, trace_frames)
            self._add_to_session(session, assocs)
            session.commit()

        self.interactive.setup()
        self.interactive.set_issue(1)
        self.assertEqual(self.interactive.current_trace_frame_index, 1)
        self.interactive.next_cursor_location()
        self.assertEqual(self.interactive.current_trace_frame_index, 2)
        self.interactive.next_cursor_location()
        self.assertEqual(self.interactive.current_trace_frame_index, 2)
        self.interactive.prev_cursor_location()
        self.assertEqual(self.interactive.current_trace_frame_index, 1)
        self.interactive.prev_cursor_location()
        self.assertEqual(self.interactive.current_trace_frame_index, 0)
        self.interactive.prev_cursor_location()
        self.assertEqual(self.interactive.current_trace_frame_index, 0)
    def testTrace(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        trace_frames = [
            TraceFrame(
                id=1,
                kind=TraceKind.POSTCONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="source",
                callee_location=SourceLocation(1, 1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=2,
                kind=TraceKind.PRECONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="sink",
                callee_location=SourceLocation(1, 1, 2),
                filename="file.py",
                run_id=1,
            ),
        ]
        assocs = [
            IssueInstanceTraceFrameAssoc(trace_frame_id=1, issue_instance_id=1),
            IssueInstanceTraceFrameAssoc(trace_frame_id=2, issue_instance_id=1),
            TraceFrameLeafAssoc(trace_frame_id=1, leaf_id=1),
            TraceFrameLeafAssoc(trace_frame_id=2, leaf_id=1),
        ]

        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            self._add_to_session(session, trace_frames)
            self._add_to_session(session, assocs)
            session.commit()

        self.interactive.setup()
        self.interactive.trace()
        stderr = self.stderr.getvalue().strip()
        self.assertIn("Use 'set_issue(ID)' to select an issue first.", stderr)

        self.interactive.set_issue(1)
        self.interactive.trace()
        output = self.stdout.getvalue().strip()
        self.assertIn("                leaf       source file.py:1|1|1", output)
        self.assertIn(" -->            call1      root   file.py:1|2|3", output)
        self.assertIn("                leaf       sink   file.py:1|1|2", output)
    def testVerifyMultipleBranches(self):
        # Leads to no-op on _generate_trace
        self.interactive.trace_tuples_id = 1
        self.interactive.current_issue_id = 1

        self.interactive.current_trace_frame_index = 0
        self.interactive.trace_tuples = [
            TraceTuple(trace_frame=TraceFrame(id=1), branches=1),
            TraceTuple(trace_frame=TraceFrame(id=2), branches=2),
        ]
        self.assertFalse(self.interactive._verify_multiple_branches())

        self.interactive.current_trace_frame_index = 1
        self.assertTrue(self.interactive._verify_multiple_branches())
    def testCurrentBranchIndex(self):
        trace_frames = [TraceFrame(id=1), TraceFrame(id=2), TraceFrame(id=3)]

        self.interactive.current_trace_frame_index = 0
        self.interactive.trace_tuples = [TraceTuple(trace_frame=TraceFrame(id=1))]

        self.assertEqual(0, self.interactive._current_branch_index(trace_frames))
        self.interactive.trace_tuples[0].trace_frame.id = 2
        self.assertEqual(1, self.interactive._current_branch_index(trace_frames))
        self.interactive.trace_tuples[0].trace_frame.id = 3
        self.assertEqual(2, self.interactive._current_branch_index(trace_frames))

        self.interactive.trace_tuples[0].trace_frame.id = 4
        self.assertEqual(-1, self.interactive._current_branch_index(trace_frames))
    def testTraceMissingFrames(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        trace_frames = [
            TraceFrame(
                id=1,
                kind=TraceKind.POSTCONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="source",
                callee_location=SourceLocation(1, 1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=2,
                kind=TraceKind.PRECONDITION,
                caller="call1",
                caller_port="root",
                callee="call2",
                callee_port="param0",
                callee_location=SourceLocation(1, 1, 1),
                filename="file.py",
                run_id=1,
            ),
        ]
        assocs = [
            IssueInstanceTraceFrameAssoc(trace_frame_id=1, issue_instance_id=1),
            IssueInstanceTraceFrameAssoc(trace_frame_id=2, issue_instance_id=1),
            TraceFrameLeafAssoc(trace_frame_id=1, leaf_id=1),
            TraceFrameLeafAssoc(trace_frame_id=2, leaf_id=1),
        ]

        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            self._add_to_session(session, trace_frames)
            self._add_to_session(session, assocs)
            session.commit()

        self.interactive.setup()
        self.interactive.set_issue(1)
        self.interactive.trace()
        stdout = self.stdout.getvalue().strip()
        self.assertIn("Missing trace frame: call2:param0", stdout)
Esempio n. 7
0
    def _navigate_trace_frames(self,
                               session: Session,
                               initial_trace_frames: List[TraceFrame],
                               index: int = 0) -> List[Tuple[TraceFrame, int]]:
        if not initial_trace_frames:
            return []

        trace_frames = [(initial_trace_frames[index],
                         len(initial_trace_frames))]
        while not self._is_leaf(trace_frames[-1]):
            trace_frame, branches = trace_frames[-1]
            next_nodes = self._next_trace_frames(session, trace_frame)

            if len(next_nodes) == 0:
                # Denote a missing frame by setting caller to None
                trace_frames.append((
                    TraceFrame(  # pyre-ignore: T41318465
                        callee=trace_frame.callee,
                        callee_port=trace_frame.callee_port,
                        caller=None,
                    ),
                    0,
                ))
                return trace_frames

            trace_frames.append((next_nodes[0], len(next_nodes)))
        return trace_frames
Esempio n. 8
0
    def _update_trace_tuples_new_parent(self, parent_trace_frame: TraceFrame) -> None:
        # Construct the placeholder trace tuple and the actual one.
        new_head = [
            TraceTuple(
                trace_frame=TraceFrame(
                    caller="unused",
                    callee=parent_trace_frame.caller,
                    callee_port=parent_trace_frame.caller_port,
                    filename=parent_trace_frame.filename,
                    callee_location=parent_trace_frame.callee_location,
                    kind=parent_trace_frame.kind,
                )
            ),
            TraceTuple(trace_frame=parent_trace_frame),
        ]

        if parent_trace_frame.kind == TraceKind.POSTCONDITION:
            # If current state is: C in [A,B,C,D,E]
            # Then new state is:   [A,B] + new_tail
            new_tail = new_head[::-1]
            self.trace_tuples = (
                self.trace_tuples[: self.current_trace_frame_index] + new_tail
            )
            self.current_trace_frame_index = len(self.trace_tuples) - 1
            return

        # If current state is: C in [A,B,C,D,E]
        # Then new state is:   new_head + [D,E]
        self.trace_tuples = (
            new_head + self.trace_tuples[self.current_trace_frame_index + 1 :]
        )
        self.current_trace_frame_index = 0
Esempio n. 9
0
    def _generate_trace(self):
        if self.trace_tuples_id == self.current_issue_id:
            return  # already generated

        with self.db.make_session() as session:
            issue_instance, issue = self._get_current_issue(session)

            postcondition_navigation = self._navigate_trace_frames(
                session,
                self._initial_trace_frames(session, issue_instance.id,
                                           TraceKind.POSTCONDITION),
            )
            precondition_navigation = self._navigate_trace_frames(
                session,
                self._initial_trace_frames(session, issue_instance.id,
                                           TraceKind.PRECONDITION),
            )

        self.trace_tuples = (
            self._create_trace_tuples(reversed(postcondition_navigation)) + [
                TraceTuple(trace_frame=TraceFrame(
                    callee=issue.callable,
                    callee_port="root",
                    filename=issue_instance.filename,
                    callee_location=issue_instance.location,
                ))
            ] + self._create_trace_tuples(precondition_navigation))
        self.trace_tuples_id = self.current_issue_id
        self.root_trace_frame_index = len(postcondition_navigation)
        self.current_trace_frame_index = self.root_trace_frame_index
Esempio n. 10
0
 def testCreateTraceTuples(self):
     # reverse order
     postcondition_traces = [
         (
             TraceFrame(
                 callee="call3",
                 callee_port="result",
                 filename="file3.py",
                 callee_location=SourceLocation(1, 1, 3),
                 caller="main",
                 caller_port="root",
             ),
             1,
         ),
         (
             TraceFrame(
                 callee="call2",
                 callee_port="result",
                 caller="dummy caller",
                 filename="file2.py",
                 callee_location=SourceLocation(1, 1, 2),
             ),
             2,
         ),
         (
             TraceFrame(
                 callee="leaf",
                 callee_port="source",
                 caller="dummy caller",
                 filename="file1.py",
                 callee_location=SourceLocation(1, 1, 1),
             ),
             3,
         ),
     ]
     trace_tuples = self.interactive._create_trace_tuples(postcondition_traces)
     self.assertEqual(len(trace_tuples), 3)
     self.assertEqual(
         trace_tuples,
         [
             TraceTuple(postcondition_traces[0][0], 1),
             TraceTuple(postcondition_traces[1][0], 2),
             TraceTuple(postcondition_traces[2][0], 3),
         ],
     )
Esempio n. 11
0
    def testListSourceCode(self):
        mock_data = """if this_is_true:
    print("This was true")
else:
    print("This was false")
        """
        self.interactive.setup()
        self.interactive.current_issue_id = 1
        self.interactive.trace_tuples_id = 1

        self.interactive.current_trace_frame_index = 0
        self.interactive.trace_tuples = [
            TraceTuple(
                trace_frame=TraceFrame(
                    filename="file.py", callee_location=SourceLocation(2, 1, 1)
                )
            )
        ]
        with patch("builtins.open", mock_open(read_data=mock_data)) as mock_file:
            self._clear_stdout()
            self.interactive.list_source_code(2)
            mock_file.assert_called_once_with(f"{os.getcwd()}/file.py", "r")
            output = self.stdout.getvalue()
            self.assertEqual(
                output.split("\n"),
                [
                    "file.py:2|1|1",
                    "     1  if this_is_true:",
                    ' --> 2      print("This was true")',
                    "     3  else:",
                    '     4      print("This was false")',
                    "",
                ],
            )

            mock_file.reset_mock()
            self._clear_stdout()
            self.interactive.list_source_code(1)
            mock_file.assert_called_once_with(f"{os.getcwd()}/file.py", "r")
            output = self.stdout.getvalue()
            self.assertEqual(
                output.split("\n"),
                [
                    "file.py:2|1|1",
                    "     1  if this_is_true:",
                    ' --> 2      print("This was true")',
                    "     3  else:",
                    "",
                ],
            )
Esempio n. 12
0
    def testListSourceCodeFileNotFound(self):
        self.interactive.setup()
        self.interactive.current_issue_id = 1
        self.interactive.trace_tuples_id = 1

        self.interactive.current_trace_frame_index = 0
        self.interactive.trace_tuples = [
            TraceTuple(
                trace_frame=TraceFrame(
                    filename="file.py", callee_location=SourceLocation(2, 1, 1)
                )
            )
        ]
        with patch("builtins.open", mock_open(read_data="not read")) as mock_file:
            mock_file.side_effect = FileNotFoundError()
            self.interactive.list_source_code()
            self.assertIn("Couldn't open", self.stderr.getvalue())
            self.assertNotIn("file.py", self.stdout.getvalue())
Esempio n. 13
0
    def _generate_raw_precondition(
        self,
        run,
        filename,
        caller,
        caller_port,
        callee,
        callee_port,
        callee_location,
        titos,
        sinks,
        type_interval,
        features,
    ):
        lb, ub, preserves_type_context = self._get_interval(type_interval)
        trace_frame = TraceFrame.Record(
            id=DBID(),
            kind=TraceKind.PRECONDITION,
            caller=caller,
            caller_port=caller_port,
            callee=callee,
            callee_port=callee_port,
            callee_location=SourceLocation(
                callee_location["line"],
                callee_location["start"],
                callee_location["end"],
            ),
            filename=filename,
            titos=titos,
            run_id=run.id,
            preserves_type_context=preserves_type_context,
            type_interval_lower=lb,
            type_interval_upper=ub,
            migrated_id=None,
        )

        for (sink, depth) in sinks:
            sink_record = self._get_shared_text(SharedTextKind.SINK, sink)
            self.graph.add_trace_frame_leaf_assoc(trace_frame, sink_record,
                                                  depth)

        self.graph.add_trace_frame(trace_frame)
        self._generate_trace_annotations(trace_frame.id, features)
        return trace_frame
Esempio n. 14
0
    def _generate_raw_postcondition(
        self,
        run,
        filename,
        caller,
        caller_port,
        callee,
        callee_port,
        callee_location,
        sources,
        type_interval,
    ):
        lb, ub, preserves_type_context = self._get_interval(type_interval)

        trace_frame = TraceFrame.Record(
            id=DBID(),
            kind=TraceKind.POSTCONDITION,
            caller=caller,
            callee=callee,
            callee_location=SourceLocation(
                callee_location["line"],
                callee_location["start"],
                callee_location["end"],
            ),
            filename=filename,
            run_id=run.id,
            caller_port=caller_port,
            callee_port=callee_port,
            preserves_type_context=preserves_type_context,
            type_interval_lower=lb,
            type_interval_upper=ub,
            migrated_id=None,
            titos=[],
        )

        for (source, depth) in sources:
            source_record = self._get_shared_text(SharedTextKind.SOURCE,
                                                  source)
            self.graph.add_trace_frame_leaf_assoc(trace_frame, source_record,
                                                  depth)

        self.graph.add_trace_frame(trace_frame)
        return trace_frame
Esempio n. 15
0
    def testTraceNoSinks(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        trace_frame = TraceFrame(
            id=1,
            kind=TraceKind.POSTCONDITION,
            caller="call1",
            caller_port="root",
            callee="leaf",
            callee_port="source",
            callee_location=SourceLocation(1, 1),
            filename="file.py",
            run_id=1,
        )
        source = SharedText(id=1, contents="source1", kind=SharedTextKind.SOURCE)
        assocs = [
            IssueInstanceTraceFrameAssoc(trace_frame_id=1, issue_instance_id=1),
            TraceFrameLeafAssoc(trace_frame_id=1, leaf_id=1),
        ]
        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            session.add(trace_frame)
            session.add(source)
            self._add_to_session(session, assocs)
            session.commit()

        self.interactive.setup()
        self.interactive.sources = {"source1"}
        self.interactive.set_issue(1)
        self._clear_stdout()
        self.interactive.trace()
        self.assertEqual(
            self.stdout.getvalue().split("\n"),
            [
                "     [branches] [callable] [port] [location]",
                "                leaf       source file.py:1|1|1",
                " -->            call1      root   file.py:1|2|3",
                "",
            ],
        )
Esempio n. 16
0
    def _generate_trace_from_frame(self):
        with self.db.make_session() as session:
            trace_frame = (
                session.query(TraceFrame)
                .filter(TraceFrame.id == self.current_frame_id)
                .scalar()
            )

            navigation = self._navigate_trace_frames(session, [trace_frame])

        # We need to "fake" another node for the selected trace frame.
        # Suppose we select a trace frame (A->B) and the generated navigation
        #   is (A->B), (B->C), (C->D) with D as leaf.
        # When we display traces, we only use the callee, so this trace would
        #   look like B->C->D. If we also want to see A->, then we need to add a
        #   placeholder.
        # Set caller to "unused", since _create_trace_tuples checks presence
        #   of a caller to determine insertion of "Missing frame".
        first_trace_frame = navigation[0][0]
        navigation.insert(
            0,
            (
                TraceFrame(
                    caller="unused",
                    callee=first_trace_frame.caller,
                    callee_port=first_trace_frame.caller_port,
                    filename=first_trace_frame.filename,
                    callee_location=first_trace_frame.callee_location,
                    kind=first_trace_frame.kind,
                ),
                1,
            ),
        )

        self.current_trace_frame_index = 0
        if trace_frame.kind == TraceKind.POSTCONDITION:
            self.current_trace_frame_index = len(navigation) - 1
            navigation = reversed(navigation)

        self.trace_tuples = self._create_trace_tuples(navigation)
Esempio n. 17
0
    def testOutputTraceTuples(self):
        trace_tuples = [
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="leaf",
                    callee_port="source",
                    filename="file1.py",
                    callee_location=SourceLocation(1, 1, 1),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="call2",
                    callee_port="result",
                    filename="file2.py",
                    callee_location=SourceLocation(1, 1, 2),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="call3",
                    callee_port="result",
                    filename="file3.py",
                    callee_location=SourceLocation(1, 1, 3),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="main",
                    callee_port="root",
                    filename="file4.py",
                    callee_location=SourceLocation(1, 1, 4),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="call4",
                    callee_port="param0",
                    filename="file4.py",
                    callee_location=SourceLocation(1, 1, 4),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="call5",
                    callee_port="param1",
                    filename="file5.py",
                    callee_location=SourceLocation(1, 1, 5),
                )
            ),
            TraceTuple(
                trace_frame=TraceFrame(
                    callee="leaf",
                    callee_port="sink",
                    filename="file6.py",
                    callee_location=SourceLocation(1, 1, 6),
                )
            ),
        ]
        self.interactive.current_trace_frame_index = 1
        self.interactive._output_trace_tuples(trace_tuples)
        output = self.stdout.getvalue()
        self.assertEqual(
            output.split("\n"),
            [
                "     [branches] [callable] [port] [location]",
                "                leaf       source file1.py:1|1|1",
                " -->            call2      result file2.py:1|1|2",
                "                call3      result file3.py:1|1|3",
                "                main       root   file4.py:1|1|4",
                "                call4      param0 file4.py:1|1|4",
                "                call5      param1 file5.py:1|1|5",
                "                leaf       sink   file6.py:1|1|6",
                "",
            ],
        )

        self._clear_stdout()
        self.interactive.current_trace_frame_index = 4
        self.interactive._output_trace_tuples(trace_tuples)
        output = self.stdout.getvalue()
        self.assertEqual(
            output.split("\n"),
            [
                "     [branches] [callable] [port] [location]",
                "                leaf       source file1.py:1|1|1",
                "                call2      result file2.py:1|1|2",
                "                call3      result file3.py:1|1|3",
                "                main       root   file4.py:1|1|4",
                " -->            call4      param0 file4.py:1|1|4",
                "                call5      param1 file5.py:1|1|5",
                "                leaf       sink   file6.py:1|1|6",
                "",
            ],
        )
Esempio n. 18
0
    def _set_up_branched_trace(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        messages = [
            SharedText(id=1, contents="source1", kind=SharedTextKind.SOURCE),
            SharedText(id=2, contents="sink1", kind=SharedTextKind.SINK),
        ]
        trace_frames = []
        assocs = [
            IssueInstanceSharedTextAssoc(issue_instance_id=1, shared_text_id=1),
            IssueInstanceSharedTextAssoc(issue_instance_id=1, shared_text_id=2),
        ]
        for i in range(6):
            trace_frames.append(
                TraceFrame(
                    id=i + 1,
                    caller="call1",
                    caller_port="root",
                    filename="file.py",
                    callee_location=SourceLocation(i, i, i),
                    run_id=1,
                )
            )
            if i < 2:  # 2 postconditions
                trace_frames[i].kind = TraceKind.POSTCONDITION
                trace_frames[i].callee = "leaf"
                trace_frames[i].callee_port = "source"
                assocs.append(
                    TraceFrameLeafAssoc(trace_frame_id=i + 1, leaf_id=1, trace_length=0)
                )
                assocs.append(
                    IssueInstanceTraceFrameAssoc(
                        trace_frame_id=i + 1, issue_instance_id=1
                    )
                )
            elif i < 4:
                trace_frames[i].kind = TraceKind.PRECONDITION
                trace_frames[i].callee = "call2"
                trace_frames[i].callee_port = "param2"
                assocs.append(
                    TraceFrameLeafAssoc(trace_frame_id=i + 1, leaf_id=2, trace_length=1)
                )
                assocs.append(
                    IssueInstanceTraceFrameAssoc(
                        trace_frame_id=i + 1, issue_instance_id=1
                    )
                )
            else:
                trace_frames[i].kind = TraceKind.PRECONDITION
                trace_frames[i].caller = "call2"
                trace_frames[i].caller_port = "param2"
                trace_frames[i].callee = "leaf"
                trace_frames[i].callee_port = "sink"
                assocs.append(
                    TraceFrameLeafAssoc(trace_frame_id=i + 1, leaf_id=2, trace_length=0)
                )

        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            self._add_to_session(session, messages)
            self._add_to_session(session, trace_frames)
            self._add_to_session(session, assocs)
            session.commit()
Esempio n. 19
0
    def testBranchPrefixLengthChanges(self):
        run = Run(id=1, date=datetime.now(), status=RunStatus.FINISHED)
        issue = self._generic_issue()
        issue_instance = self._generic_issue_instance()
        messages = [
            SharedText(id=1, contents="source1", kind=SharedTextKind.SOURCE),
            SharedText(id=2, contents="sink1", kind=SharedTextKind.SINK),
        ]
        trace_frames = [
            TraceFrame(
                id=1,
                kind=TraceKind.POSTCONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="source",
                callee_location=SourceLocation(1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=2,
                kind=TraceKind.POSTCONDITION,
                caller="call1",
                caller_port="root",
                callee="prev_call",
                callee_port="result",
                callee_location=SourceLocation(1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=3,
                kind=TraceKind.POSTCONDITION,
                caller="prev_call",
                caller_port="result",
                callee="leaf",
                callee_port="source",
                callee_location=SourceLocation(1, 1),
                filename="file.py",
                run_id=1,
            ),
            TraceFrame(
                id=4,
                kind=TraceKind.PRECONDITION,
                caller="call1",
                caller_port="root",
                callee="leaf",
                callee_port="sink",
                callee_location=SourceLocation(1, 2),
                filename="file.py",
                run_id=1,
            ),
        ]
        assocs = [
            IssueInstanceSharedTextAssoc(issue_instance_id=1, shared_text_id=1),
            IssueInstanceSharedTextAssoc(issue_instance_id=1, shared_text_id=2),
            IssueInstanceTraceFrameAssoc(issue_instance_id=1, trace_frame_id=1),
            IssueInstanceTraceFrameAssoc(issue_instance_id=1, trace_frame_id=2),
            IssueInstanceTraceFrameAssoc(issue_instance_id=1, trace_frame_id=4),
            TraceFrameLeafAssoc(trace_frame_id=1, leaf_id=1, trace_length=0),
            TraceFrameLeafAssoc(trace_frame_id=2, leaf_id=1, trace_length=1),
            TraceFrameLeafAssoc(trace_frame_id=3, leaf_id=1, trace_length=0),
            TraceFrameLeafAssoc(trace_frame_id=4, leaf_id=2, trace_length=0),
        ]
        with self.db.make_session() as session:
            session.add(run)
            session.add(issue)
            session.add(issue_instance)
            self._add_to_session(session, messages)
            self._add_to_session(session, trace_frames)
            self._add_to_session(session, assocs)
            session.commit()

        self.interactive.setup()
        self.interactive.set_issue(1)

        self._clear_stdout()
        self.interactive.prev_cursor_location()
        self.assertEqual(
            self.stdout.getvalue().split("\n"),
            [
                "     [branches] [callable] [port] [location]",
                " --> + 2        leaf       source file.py:1|1|1",
                "                call1      root   file.py:1|2|3",
                "                leaf       sink   file.py:1|2|2",
                "",
            ],
        )

        self._clear_stdout()
        self.interactive.branch(1)
        self.assertEqual(
            self.stdout.getvalue().split("\n"),
            [
                "     [branches] [callable] [port] [location]",
                "                leaf       source file.py:1|1|1",
                " --> + 2        prev_call  result file.py:1|1|1",
                "                call1      root   file.py:1|2|3",
                "                leaf       sink   file.py:1|2|2",
                "",
            ],
        )

        self._clear_stdout()
        self.interactive.expand()
        output = self.stdout.getvalue().strip()
        self.assertIn("[*] prev_call : result", output)
        self.assertIn("        [1 hops: source1]", output)