diff --git a/OOMAnalyser.html b/OOMAnalyser.html
index 1ed8f77..e75b1b8 100644
--- a/OOMAnalyser.html
+++ b/OOMAnalyser.html
@@ -816,6 +816,7 @@ function read_and_display_file(file) {
General
- Improve SVG chart colour palette
+ - Add Selenium based unit tests
- ...
diff --git a/OOMAnalyser.py b/OOMAnalyser.py
index 8e0c365..5b79bd6 100644
--- a/OOMAnalyser.py
+++ b/OOMAnalyser.py
@@ -14,7 +14,34 @@ VERSION = "0.5.0 (devel)"
"""Version number"""
# __pragma__ ('skip')
-js_undefined = 0 # Prevent complaints by optional static checker
+# MOC objects to satisfy statical checker and imports in unit tests
+js_undefined = 0
+class classList():
+
+ def add(self, *args, **kwargs):
+ pass
+
+ def remove(self, *args, **kwargs):
+ pass
+
+
+class document():
+
+ def querySelectorAll(self, *args, **kwargs):
+ return [element()]
+
+ def getElementById(self, *arg, **kwargs):
+ return element()
+
+
+class element():
+
+ firstChild = []
+ classList = classList()
+ offsetWidth = 0
+
+ def removeChild(self, *args, **kwargs):
+ return
# __pragma__ ('noskip')
diff --git a/test.py b/test.py
index 7717c34..bbeb6f2 100755
--- a/test.py
+++ b/test.py
@@ -23,7 +23,10 @@ import socketserver
import threading
import unittest
from selenium import webdriver
+from selenium.common.exceptions import *
+import warnings
+import OOMAnalyser
class MyRequestHandler(http.server.SimpleHTTPRequestHandler):
@@ -41,29 +44,91 @@ class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
-class TestHTTPOOMAnalyser(unittest.TestCase):
- """Test OOM web page"""
+class TestBase(unittest.TestCase):
+
+ def get_lines(self, text, count):
+ """
+ Return the number of lines specified by count from given text
+ @type text: str
+ @type count: int
+ """
+ lines = text.splitlines()[:count]
+ res = '\n'.join(lines)
+ return res
+
+ def get_first_line(self, text):
+ """
+ Return the first line of the given text
+ @type text: str
+ """
+ return self.get_lines(text, 1)
+
+ def get_last_line(self, text):
+ """
+ Return the last line of the given text
+ @type text: str
+ """
+ return self.get_lines(text, -1)
+
+class TestInBrowser(TestBase):
+ """Test OOM web page in a browser"""
+
+ def setUp(self):
+ warnings.simplefilter("ignore", ResourceWarning)
- @classmethod
- def setUpClass(cls):
ThreadedTCPServer.allow_reuse_address = True
- cls.httpd = ThreadedTCPServer(('127.0.0.1', 8000), MyRequestHandler)
- # cls.httpd.allow_reuse_address = True
- server_thread = threading.Thread(target=cls.httpd.serve_forever, args=(0.1,))
+ self.httpd = ThreadedTCPServer(('127.0.0.1', 8000), MyRequestHandler)
+ server_thread = threading.Thread(target=self.httpd.serve_forever, args=(0.1,))
server_thread.daemon = True
server_thread.start()
- @classmethod
- def tearDownClass(cls):
- cls.httpd.shutdown()
- cls.httpd.server_close()
-
- def setUp(self):
self.driver = webdriver.Chrome()
self.driver.get("http://127.0.0.1:8000/OOMAnalyser.html")
def tearDown(self):
self.driver.close()
+ self.httpd.shutdown()
+ self.httpd.server_close()
+
+ def assert_on_warn(self):
+ notify_box = self.driver.find_element_by_id('notify_box')
+ with self.assertRaises(NoSuchElementException):
+ notify_box.find_element_by_class_name('js-notify_box__msg--warning')
+
+ def assert_on_error(self):
+ notify_box = self.driver.find_element_by_id('notify_box')
+ with self.assertRaises(NoSuchElementException):
+ notify_box.find_element_by_class_name('js-notify_box__msg--error')
+
+ def click_analyse(self):
+ analyse = self.driver.find_element_by_xpath('//button[text()="Analyse"]')
+ analyse.click()
+
+ def click_reset(self):
+ reset = self.driver.find_element_by_xpath('//button[text()="Reset"]')
+ reset.click()
+ self.assert_on_warn_error()
+
+ def analyse_oom(self, text):
+ """
+ Insert text and run analysis
+
+ :param str text: OOM text to analyse
+ """
+ textarea = self.driver.find_element_by_id('textarea_oom')
+ self.assertEqual(textarea.get_attribute('value'), '', 'Empty textarea expected')
+ textarea.send_keys(text)
+
+ self.assertNotEqual(textarea.get_attribute('value'), '', 'Missing OOM text in textarea')
+
+ h3_summary = self.driver.find_element_by_xpath('//h3[text()="Summary"]')
+ self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
+
+ self.click_analyse()
+
+ def assert_on_warn_error(self):
+ self.assert_on_warn()
+ self.assert_on_error()
def test_001_load_page(self):
"""Test if the page is loading"""
@@ -85,9 +150,9 @@ class TestHTTPOOMAnalyser(unittest.TestCase):
h3_summary = self.driver.find_element_by_xpath('//h3[text()="Summary"]')
self.assertFalse(h3_summary.is_displayed(), "Analysis details incl. Summary
should be not displayed")
- analyse = self.driver.find_element_by_xpath('//button[text()="Analyse"]')
- analyse.click()
+ self.click_analyse()
+ self.assert_on_warn_error()
self.assertTrue(h3_summary.is_displayed(), "Analysis details incl. Summary
should be displayed")
trigger_proc_name = self.driver.find_element_by_class_name('trigger_proc_name')
@@ -98,6 +163,71 @@ class TestHTTPOOMAnalyser(unittest.TestCase):
killed_proc_score = self.driver.find_element_by_class_name('killed_proc_score')
self.assertEqual(killed_proc_score.text, '651', 'Unexpected OOM score of killed process')
+ def test_004_begin_but_no_end(self):
+ """Test incomplete OOM text - just the beginning"""
+ example = """\
+sed invoked oom-killer: gfp_mask=0x201da, order=0, oom_score_adj=0
+sed cpuset=/ mems_allowed=0-1
+ """
+ self.analyse_oom(example)
+
+ notify_box = self.driver.find_element_by_id('notify_box')
+ notify_box.find_element_by_class_name('js-notify_box__msg--warning')
+ self.assertTrue(notify_box.text.startswith("WARNING: The inserted OOM is incomplete!"))
+
+ self.click_reset()
+
+ def test_005_no_begin_but_end(self):
+ """Test incomplete OOM text - just the end"""
+ example = """\
+Out of memory: Kill process 6576 (java) score 651 or sacrifice child
+Killed process 6576 (java) total-vm:33914892kB, anon-rss:20629004kB, file-rss:0kB, shmem-rss:0kB
+ """
+ self.analyse_oom(example)
+
+ notify_box = self.driver.find_element_by_id('notify_box')
+ notify_box.find_element_by_class_name('js-notify_box__msg--error')
+ self.assertTrue(notify_box.text.startswith("ERROR: The inserted text is not a valid OOM block!"))
+
+ self.click_reset()
+
+ def test_006_trigger_proc_space(self):
+ """Test trigger process name contains a space"""
+ pass
+
+ def test_007_kill_proc_space(self):
+ """Test killed process name contains a space"""
+ pass
+
+
+class TestPython(TestBase):
+
+ def test_001_trigger_proc_space(self):
+ """Test RE to find name of trigger process"""
+ first = self.get_first_line(OOMAnalyser.OOMDisplay.example)
+ rec = OOMAnalyser.OOMAnalyser.REC_INVOKED_OOMKILLER
+ match = rec.search(first)
+ self.assertTrue(match, 'Error: re.search(REC_INVOKED_OOMKILLER) failed for simple '
+ 'process name')
+
+ first = first.replace('sed', 'VM Monitoring Task')
+ match = rec.search(first)
+ self.assertTrue(match, 'Error: re.search(REC_INVOKED_OOMKILLER) failed for process name '
+ 'with space')
+
+ def test_002_killed_proc_space(self):
+ """Test RE to find name of killed process"""
+ last = self.get_last_line(OOMAnalyser.OOMDisplay.example)
+ rec = OOMAnalyser.OOMAnalyser.REC_OOM_KILL_PROCESS
+ match = rec.search(last)
+ self.assertTrue(match, 'Error: re.search(REC_OOM_KILL_PROCESS) failed for simple '
+ 'process name')
+
+ last = last.replace('sed', 'VM Monitoring Task')
+ match = rec.search(last)
+ self.assertTrue(match, 'Error: re.search(REC_OOM_KILL_PROCESS) failed for process name '
+ 'with space')
+
if __name__ == "__main__":
# import logging, sys