diff options
Diffstat (limited to 'src/ToolBox/SOS/tests')
-rw-r--r-- | src/ToolBox/SOS/tests/.gitmirrorall | 1 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/OnCrash.do | 2 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/README.md | 33 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/dumpil.py | 26 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/dumpmodule.py | 26 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/runprocess.py | 34 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/test_libsosplugin.py | 84 | ||||
-rw-r--r-- | src/ToolBox/SOS/tests/testutils.py | 40 |
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 |