From 04002ab6e6f5b8ee94a0e6d76ecabb42d799f40d Mon Sep 17 00:00:00 2001 From: Carsten Grohmann Date: Sat, 17 Jul 2021 17:29:40 +0200 Subject: [PATCH] Extent unit tests --- OOMAnalyser.html | 1 + OOMAnalyser.py | 29 ++++++++- test.py | 160 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 174 insertions(+), 16 deletions(-) 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

  1. Improve SVG chart colour palette
  2. +
  3. Add Selenium based unit tests
  4. ...
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