import lldb
from intelpt_testcase import *
from lldbsuite.test.lldbtest import *
from lldbsuite.test import lldbutil
from lldbsuite.test.decorators import *


@skipIfNoIntelPT
class TestTraceStartStop(TraceIntelPTTestCaseBase):
    def expectGenericHelpMessageForStartCommand(self):
        self.expect(
            "help thread trace start",
            substrs=["Syntax: thread trace start [<trace-options>]"],
        )

    @testSBAPIAndCommands
    def testStartStopSessionFileThreads(self):
        # it should fail for processes from json session files
        self.expect(
            "trace load -v "
            + os.path.join(self.getSourceDir(), "intelpt-trace", "trace.json")
        )

        # the help command should be the generic one, as it's not a live process
        self.expectGenericHelpMessageForStartCommand()

        self.traceStartThread(error=True)

        self.traceStopThread(error=True)

    @testSBAPIAndCommands
    def testStartWithNoProcess(self):
        self.traceStartThread(error=True)

    @testSBAPIAndCommands
    def testStartSessionWithWrongSize(self):
        self.expect(
            "file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )
        self.expect("b main")
        self.expect("r")

        self.traceStartThread(
            error=True,
            iptTraceSize=2000,
            substrs=["The intel pt trace size must be a power of 2", "It was 2000"],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize=5000,
            substrs=["The intel pt trace size must be a power of 2", "It was 5000"],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize=0,
            substrs=["The intel pt trace size must be a power of 2", "It was 0"],
        )

        self.traceStartThread(iptTraceSize=1048576)

    @testSBAPIAndCommands
    def testStartSessionWithSizeDeclarationInUnits(self):
        self.expect(
            "file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )
        self.expect("b main")
        self.expect("r")

        self.traceStartThread(
            error=True,
            iptTraceSize="abc",
            substrs=["invalid bytes expression for 'abc'"],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="123.12",
            substrs=["invalid bytes expression for '123.12'"],
        )

        self.traceStartThread(
            error=True, iptTraceSize='""', substrs=["invalid bytes expression for ''"]
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="2000B",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 2000"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3MB",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3MiB",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3mib",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3M",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3145728"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3KB",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3KiB",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3K",
            substrs=[
                "The intel pt trace size must be a power of 2 greater than or equal to 4096 (2^12) bytes. It was 3072"
            ],
        )

        self.traceStartThread(
            error=True,
            iptTraceSize="3MS",
            substrs=["invalid bytes expression for '3MS'"],
        )

        self.traceStartThread(iptTraceSize="1048576")

    @skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
    def testSBAPIHelp(self):
        self.expect(
            "file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )
        self.expect("b main")
        self.expect("r")

        help = self.getTraceOrCreate().GetStartConfigurationHelp()
        self.assertIn("iptTraceSize", help)
        self.assertIn("processBufferSizeLimit", help)

    @skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
    def testStoppingAThread(self):
        self.expect(
            "file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )
        self.expect("b main")
        self.expect("r")
        self.expect("thread trace start")
        self.expect("n")
        self.expect(
            "thread trace dump instructions",
            substrs=[
                """0x0000000000400511    movl   $0x0, -0x4(%rbp)
    no more data"""
            ],
        )
        # process stopping should stop the thread
        self.expect("process trace stop")
        self.expect("n")
        self.expect(
            "thread trace dump instructions", substrs=["not traced"], error=True
        )

    @skipIf(oslist=no_match(["linux"]), archs=no_match(["i386", "x86_64"]))
    def testStartStopLiveThreads(self):
        # The help command should be the generic one if there's no process running
        self.expectGenericHelpMessageForStartCommand()

        self.expect(
            "thread trace start", error=True, substrs=["error: Process not available"]
        )

        self.expect(
            "file " + os.path.join(self.getSourceDir(), "intelpt-trace", "a.out")
        )
        self.expect("b main")

        self.expect(
            "thread trace start", error=True, substrs=["error: Process not available"]
        )

        # The help command should be the generic one if there's still no process running
        self.expectGenericHelpMessageForStartCommand()

        self.expect("r")

        # This fails because "trace start" hasn't been called yet
        self.expect(
            "thread trace stop",
            error=True,
            substrs=["error: Process is not being traced"],
        )

        # the help command should be the intel-pt one now
        self.expect(
            "help thread trace start",
            substrs=[
                "Start tracing one or more threads with intel-pt.",
                "Syntax: thread trace start [<thread-index> <thread-index> ...] [<intel-pt-options>]",
            ],
        )

        # We start tracing with a small buffer size
        self.expect("thread trace start 1 --size 4096")

        # We fail if we try to trace again
        self.expect(
            "thread trace start",
            error=True,
            substrs=["error: Thread ", "already traced"],
        )

        # We can reconstruct the single instruction executed in the first line
        self.expect("n")
        self.expect(
            "thread trace dump instructions -f",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 4 at main.cpp:2
    2: {ADDRESS_REGEX}    movl"""
            ],
        )

        # We can reconstruct the instructions up to the second line
        self.expect("n")
        self.expect(
            "thread trace dump instructions -f",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 4 at main.cpp:2
    2: {ADDRESS_REGEX}    movl .*
  a.out`main \+ 11 at main.cpp:4
    4: {ADDRESS_REGEX}    movl .*
    6: {ADDRESS_REGEX}    jmp  .* ; <\+28> at main.cpp:4
    8: {ADDRESS_REGEX}    cmpl .*
    10: {ADDRESS_REGEX}    jle  .* ; <\+20> at main.cpp:5"""
            ],
        )

        self.expect(
            "thread trace dump instructions",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 32 at main.cpp:4
    10: {ADDRESS_REGEX}    jle  .* ; <\+20> at main.cpp:5
    8: {ADDRESS_REGEX}    cmpl .*
    6: {ADDRESS_REGEX}    jmp  .* ; <\+28> at main.cpp:4
    4: {ADDRESS_REGEX}    movl .*
  a.out`main \+ 4 at main.cpp:2
    2: {ADDRESS_REGEX}    movl .* """
            ],
        )

        # We stop tracing
        self.expect("thread trace stop")

        # We can't stop twice
        self.expect(
            "thread trace stop",
            error=True,
            substrs=["error: Thread ", "not currently traced"],
        )

        # We trace again from scratch, this time letting LLDB to pick the current
        # thread
        self.expect("thread trace start")
        self.expect("n")
        self.expect(
            "thread trace dump instructions -f",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 20 at main.cpp:5
    2: {ADDRESS_REGEX}    xorl"""
            ],
        )

        self.expect(
            "thread trace dump instructions",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 20 at main.cpp:5
    2: {ADDRESS_REGEX}    xorl"""
            ],
        )

        self.expect("c")
        # Now the process has finished, so the commands should fail
        self.expect(
            "thread trace start",
            error=True,
            substrs=["error: Process must be launched"],
        )

        self.expect(
            "thread trace stop", error=True, substrs=["error: Process must be launched"]
        )

        # We should be able to trace the program if we relaunch it
        # For this, we'll trace starting at a different point in the new
        # process.
        self.expect("breakpoint disable")
        self.expect("b main.cpp:4")
        self.expect("r")
        self.expect("thread trace start")
        # We can reconstruct the single instruction executed in the first line
        self.expect("si")
        self.expect(
            "thread trace dump instructions -c 1",
            patterns=[
                rf"""thread #1: tid = .*
  a.out`main \+ 11 at main.cpp:4"""
            ],
        )
