summaryrefslogtreecommitdiff
path: root/src/ToolBox/SOS/tests
diff options
context:
space:
mode:
Diffstat (limited to 'src/ToolBox/SOS/tests')
-rw-r--r--src/ToolBox/SOS/tests/.gitmirrorall1
-rw-r--r--src/ToolBox/SOS/tests/OnCrash.do2
-rw-r--r--src/ToolBox/SOS/tests/README.md33
-rw-r--r--src/ToolBox/SOS/tests/dumpil.py26
-rw-r--r--src/ToolBox/SOS/tests/dumpmodule.py26
-rw-r--r--src/ToolBox/SOS/tests/runprocess.py34
-rw-r--r--src/ToolBox/SOS/tests/test_libsosplugin.py84
-rw-r--r--src/ToolBox/SOS/tests/testutils.py40
8 files changed, 246 insertions, 0 deletions
diff --git a/src/ToolBox/SOS/tests/.gitmirrorall b/src/ToolBox/SOS/tests/.gitmirrorall
new file mode 100644
index 0000000000..9ee5c57b99
--- /dev/null
+++ b/src/ToolBox/SOS/tests/.gitmirrorall
@@ -0,0 +1 @@
+This folder will be mirrored by the Git-TFS Mirror recursively. \ No newline at end of file
diff --git a/src/ToolBox/SOS/tests/OnCrash.do b/src/ToolBox/SOS/tests/OnCrash.do
new file mode 100644
index 0000000000..b1da86154a
--- /dev/null
+++ b/src/ToolBox/SOS/tests/OnCrash.do
@@ -0,0 +1,2 @@
+script open("/tmp/flag_fail", "a").close()
+q
diff --git a/src/ToolBox/SOS/tests/README.md b/src/ToolBox/SOS/tests/README.md
new file mode 100644
index 0000000000..ed4c8d062c
--- /dev/null
+++ b/src/ToolBox/SOS/tests/README.md
@@ -0,0 +1,33 @@
+Testing libsosplugin
+=====================================
+
+**Test assembly**
+The test asembly file must follow two rules:
+1. the test class must be named `Program` and
+2. it must have a static `Main` method.
+
+**Running tests**
+Make sure that python's lldb module is accessible. To run the tests, use the following command:
+`python test_libsosplugin.py --clr-args="/path/to/corerun [corerun_options] /path/to/test_assembly.exe"`
+`--clr-args` is a command that would normally be used to launch `corerun`
+This will run the test suite on the specified assembly.
+
+**Writing tests**
+Tests start with the `TestSosCommands` class defined in `test_libsosplugin.py`. To add a test to the suite, start with implementing a new method inside this class whose name begins with `test_`. Most new commands will require only one line of code in this method: `self.do_test("scenarioname")`. This command will launch a new `lldb` instance, which in turn will call the `runScenario` method from `scenarioname` module. `scenarioname` is the name of the python module that will be running the scenario inside `lldb` (found in `tests` folder alongside with `test_libsosplugin.py` and named `scenarioname.py`).
+An example of a scenario looks like this:
+
+ import lldb
+ def runScenario(assemblyName, debugger, target):
+ process = target.GetProcess()
+
+ # do some work
+
+ process.Continue()
+ return True
+
+ `runScenario` method does all the work related to running the scenario: setting breakpoints, running SOS commands and examining their output. It should return a boolean value indicating a success or a failure.
+***Note:*** `testutils.py` defines some useful commands that can be reused in many scenarios.
+
+**Useful links**
+[Python scripting in LLDB](http://lldb.llvm.org/python-reference.html)
+[Python unittest framework](https://docs.python.org/2.7/library/unittest.html) \ No newline at end of file
diff --git a/src/ToolBox/SOS/tests/dumpil.py b/src/ToolBox/SOS/tests/dumpil.py
new file mode 100644
index 0000000000..9539b618bb
--- /dev/null
+++ b/src/ToolBox/SOS/tests/dumpil.py
@@ -0,0 +1,26 @@
+import lldb
+import lldbutil
+import re
+import os
+import testutils
+
+def runScenario(assemblyName, debugger, target):
+ process = target.GetProcess()
+ res = lldb.SBCommandReturnObject()
+ ci = debugger.GetCommandInterpreter()
+
+ testutils.stop_in_main(ci, process, assemblyName)
+ addr = testutils.exec_and_find(ci, "name2ee " + assemblyName + " Program.Main", "MethodDesc:\s+([0-9a-fA-F]+)")
+
+ result = False
+ if addr is not None:
+ ci.HandleCommand("dumpil " + addr, res)
+ if res.Succeeded():
+ result = True
+ else:
+ print("DumpIL failed:")
+ print(res.GetOutput())
+ print(res.GetError())
+
+ process.Continue()
+ return result
diff --git a/src/ToolBox/SOS/tests/dumpmodule.py b/src/ToolBox/SOS/tests/dumpmodule.py
new file mode 100644
index 0000000000..04a5764752
--- /dev/null
+++ b/src/ToolBox/SOS/tests/dumpmodule.py
@@ -0,0 +1,26 @@
+import lldb
+import lldbutil
+import re
+import os
+import testutils
+
+def runScenario(assemblyName, debugger, target):
+ process = target.GetProcess()
+ res = lldb.SBCommandReturnObject()
+ ci = debugger.GetCommandInterpreter()
+
+ testutils.stop_in_main(ci, process, assemblyName)
+ addr = testutils.exec_and_find(ci, "name2ee " + assemblyName + " Program.Main", "Module:\s+([0-9a-fA-F]+)")
+
+ result = False
+ if addr is not None:
+ ci.HandleCommand("dumpmodule " + addr, res)
+ if res.Succeeded():
+ result = True
+ else:
+ print("DumpModule failed:")
+ print(res.GetOutput())
+ print(res.GetError())
+
+ process.Continue()
+ return result \ No newline at end of file
diff --git a/src/ToolBox/SOS/tests/runprocess.py b/src/ToolBox/SOS/tests/runprocess.py
new file mode 100644
index 0000000000..d9367b3e6c
--- /dev/null
+++ b/src/ToolBox/SOS/tests/runprocess.py
@@ -0,0 +1,34 @@
+import os
+import lldb
+import sys
+import importlib
+from test_libsosplugin import fail_flag
+
+def run(assemblyName, moduleName):
+ global fail_flag
+
+ print(fail_flag)
+ # set the flag, if it is not set
+ if not os.access(fail_flag, os.R_OK):
+ open(fail_flag, "a").close()
+
+
+ debugger = lldb.debugger
+
+ debugger.SetAsync(False)
+ target = lldb.target
+
+ debugger.HandleCommand("process launch -s")
+ debugger.HandleCommand("breakpoint set -n LoadLibraryExW")
+
+ target.GetProcess().Continue()
+
+ debugger.HandleCommand("breakpoint delete 1")
+ #run the scenario
+ print("starting scenario...")
+ i = importlib.import_module(moduleName)
+ scenarioResult = i.runScenario(os.path.basename(assemblyName), debugger, target)
+
+ # clear the failed flag if the exit status is OK
+ if scenarioResult is True and target.GetProcess().GetExitStatus() == 0:
+ os.unlink(fail_flag)
diff --git a/src/ToolBox/SOS/tests/test_libsosplugin.py b/src/ToolBox/SOS/tests/test_libsosplugin.py
new file mode 100644
index 0000000000..e4f59ebbcf
--- /dev/null
+++ b/src/ToolBox/SOS/tests/test_libsosplugin.py
@@ -0,0 +1,84 @@
+import unittest
+import argparse
+import re
+import tempfile
+import subprocess
+import threading
+import os
+import os.path
+import sys
+
+assemblyName=''
+clrArgs=''
+fail_flag='/tmp/fail_flag'
+
+# helper functions
+
+def prepareScenarioFile(moduleName):
+ global assemblyName
+ #create a temporary scenario file
+ fd, scenarioFileName = tempfile.mkstemp()
+ scenarioFile = open(scenarioFileName, 'w')
+ scenarioFile.write('script from runprocess import run\n')
+ scenarioFile.write('script run("'+assemblyName+'", "'+moduleName+'")\n')
+ scenarioFile.write('quit\n')
+ scenarioFile.close()
+ os.close(fd)
+ return scenarioFileName
+
+def runWithTimeout(cmd, timeout):
+ d = {'process': None}
+ def run():
+ d['process'] = subprocess.Popen(cmd, shell=True)
+ d['process'].communicate()
+
+ thread = threading.Thread(target=run)
+ thread.start()
+
+ thread.join(timeout)
+ if thread.is_alive():
+ d['process'].terminate()
+ thread.join()
+
+# Test class
+class TestSosCommands(unittest.TestCase):
+
+ def do_test(self, command):
+ global clrArgs
+ global fail_flag
+ filename = prepareScenarioFile(command)
+ cmd = "lldb --source "+filename+" -b -K \"OnCrash.do\" -- "+clrArgs+" > "+command+".log 2>"+command+".log.2"
+ runWithTimeout(cmd, 120)
+ self.assertFalse(os.path.isfile(fail_flag))
+ os.unlink(filename)
+
+ def test_dumpmodule(self):
+ self.do_test("dumpmodule")
+
+ def test_dumpil(self):
+ self.do_test("dumpil")
+
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--clr-args', default='')
+ parser.add_argument('unittest_args', nargs='*')
+
+ args = parser.parse_args()
+
+ clrArgs = args.clr_args
+ print("ClrArgs: " + clrArgs)
+ # find assembly name among lldb arguments
+ assembly_regexp = re.compile("([^\s]+\.exe)")
+ assemblyMatch = assembly_regexp.search(clrArgs)
+ if assemblyMatch is not None:
+ assemblyName = assemblyMatch.group(1)
+ else:
+ print("Assembly not recognized")
+ exit(1)
+
+ print("Assembly name: "+assemblyName)
+ sys.argv[1:] = args.unittest_args
+ suite = unittest.TestLoader().loadTestsFromTestCase(TestSosCommands)
+ unittest.TextTestRunner(verbosity=2).run(suite)
+ os.unlink(fail_flag) \ No newline at end of file
diff --git a/src/ToolBox/SOS/tests/testutils.py b/src/ToolBox/SOS/tests/testutils.py
new file mode 100644
index 0000000000..1ddb6560e6
--- /dev/null
+++ b/src/ToolBox/SOS/tests/testutils.py
@@ -0,0 +1,40 @@
+import lldb
+import re
+
+def checkResult(res):
+ if not res.Succeeded():
+ print(res.GetOutput())
+ print(res.GetError())
+ exit(1)
+
+def exec_and_find(commandInterpreter, cmd, regexp):
+ res = lldb.SBCommandReturnObject()
+ commandInterpreter.HandleCommand(cmd, res)
+ checkResult(res)
+
+ expr = re.compile(regexp)
+ addr = None
+
+ print(res.GetOutput())
+ lines = res.GetOutput().splitlines()
+ for line in lines:
+ match = expr.match(line)
+ if match is not None:
+ addr = match.group(1)
+ break
+
+ print("Found addr: " + str(addr))
+ return addr
+
+def stop_in_main(commandInterpreter, process, assemblyName):
+ res = lldb.SBCommandReturnObject()
+ commandInterpreter.HandleCommand("bpmd " + assemblyName + " Program.Main", res)
+ checkResult(res)
+ print(res.GetOutput())
+ print(res.GetError())
+ res.Clear()
+
+
+ # Use Python API to continue the process. The listening thread should be
+ # able to receive the state changed events.
+ process.Continue() \ No newline at end of file