summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.gitignore1
-rwxr-xr-xtest/monkey-driver.py77
-rw-r--r--test/monkey-tests/resource-scheme.yaml6
-rw-r--r--test/monkeyfarmer.py390
4 files changed, 472 insertions, 2 deletions
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 000000000..bee8a64b7
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+__pycache__
diff --git a/test/monkey-driver.py b/test/monkey-driver.py
index 25c6422f4..eed7da0e7 100755
--- a/test/monkey-driver.py
+++ b/test/monkey-driver.py
@@ -2,6 +2,8 @@
import sys, getopt, yaml
+from monkeyfarmer import Browser
+
def print_usage():
print('Usage:')
print(' ' + sys.argv[0] + ' -m <path to monkey> -t <path to test>')
@@ -47,23 +49,68 @@ def get_indent(ctx):
def print_test_plan_info(ctx, plan):
print('Running test: [' + plan["group"] + '] ' + plan["title"])
+def assert_browser(ctx):
+ assert(ctx['browser'].started)
+ assert(not ctx['browser'].stopped)
+
def run_test_step_action_launch(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ assert(ctx.get('browser') is None)
+ assert(ctx.get('windows') is None)
+ ctx['browser'] = Browser(monkey_cmd=[ctx["monkey"]], quiet=True)
+ assert_browser(ctx)
+ ctx['windows'] = dict()
def run_test_step_action_window_new(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ tag = step['tag']
+ assert_browser(ctx)
+ assert(ctx['windows'].get(tag) is None)
+ ctx['windows'][tag] = ctx['browser'].new_window(url=step.get('url'))
def run_test_step_action_window_close(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ tag = step['window']
+ assert(ctx['windows'].get(tag) is not None)
+ win = ctx['windows'].pop(tag)
+ win.kill()
+ win.wait_until_dead()
+ assert(win.alive == False)
def run_test_step_action_navigate(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ tag = step['window']
+ win = ctx['windows'].get(tag)
+ assert(win is not None)
+ win.go(step['url'])
def run_test_step_action_sleep_ms(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
def run_test_step_action_block(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ conds = step['conditions']
+ assert_browser(ctx)
+
+ def conds_met():
+ for cond in conds:
+ status = cond['status']
+ window = cond['window']
+ assert(status == "complete") # TODO: Add more status support?
+ if window == "*all*":
+ for win in ctx['windows'].items():
+ if win.throbbing:
+ return False
+ else:
+ win = ctx['windows'][window]
+ if win.throbbing:
+ return False
+ return True
+
+ while not conds_met():
+ ctx['browser'].farmer.loop(once=True)
def run_test_step_action_repeat(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
@@ -72,6 +119,28 @@ def run_test_step_action_repeat(ctx, step):
run_test_step(ctx, step)
ctx["depth"] -= 1
+def run_test_step_action_plot_check(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ win = ctx['windows'][step['window']]
+ checks = step['checks']
+ all_text = []
+ bitmaps = []
+ for plot in win.redraw():
+ if plot[0] == 'TEXT':
+ all_text.extend(plot[6:])
+ if plot[0] == 'BITMAP':
+ bitmaps.append(plot[1:])
+ all_text = " ".join(all_text)
+ for check in checks:
+ if 'text-contains' in check.keys():
+ print("Check {} in {}".format(repr(check['text-contains']),repr(all_text)))
+ assert(check['text-contains'] in all_text)
+ elif 'bitmap-count' in check.keys():
+ assert(len(bitmaps) == int(check['bitmap-count']))
+ else:
+ raise AssertionError("Unknown check: {}".format(repr(check)))
+
def run_test_step_action_timer_start(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
@@ -83,6 +152,10 @@ def run_test_step_action_timer_check(ctx, step):
def run_test_step_action_quit(ctx, step):
print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ browser = ctx.pop('browser')
+ windows = ctx.pop('windows')
+ assert(browser.quit_and_wait())
step_handlers = {
"launch": run_test_step_action_launch,
@@ -95,6 +168,7 @@ step_handlers = {
"timer-start": run_test_step_action_timer_start,
"timer-stop": run_test_step_action_timer_stop,
"timer-check": run_test_step_action_timer_check,
+ "plot-check": run_test_step_action_plot_check,
"quit": run_test_step_action_quit,
}
@@ -111,9 +185,10 @@ def main(argv):
ctx = {}
path_monkey, path_test = parse_argv(argv)
plan = load_test_plan(path_test)
+ ctx["monkey"] = path_monkey
print_test_plan_info(ctx, plan)
walk_test_plan(ctx, plan)
# Some python weirdness to get to main().
if __name__ == "__main__":
- main(sys.argv[1:]) \ No newline at end of file
+ main(sys.argv[1:])
diff --git a/test/monkey-tests/resource-scheme.yaml b/test/monkey-tests/resource-scheme.yaml
index 58b0f5eff..791a79cd6 100644
--- a/test/monkey-tests/resource-scheme.yaml
+++ b/test/monkey-tests/resource-scheme.yaml
@@ -13,6 +13,8 @@ steps:
- window: win1
status: complete
- action: plot-check
+ window: win1
+ checks:
- text-contains: Not found
- text-contains: Error 404
- action: navigate
@@ -23,8 +25,10 @@ steps:
- window: win1
status: complete
- action: plot-check
+ window: win1
+ checks:
- bitmap-count: 1
- action: window-close
- - window: win1
+ window: win1
- action: quit
diff --git a/test/monkeyfarmer.py b/test/monkeyfarmer.py
new file mode 100644
index 000000000..239a63e0e
--- /dev/null
+++ b/test/monkeyfarmer.py
@@ -0,0 +1,390 @@
+# Copyright 2017, 2018 Daniel Silverstone <dsilvers@digital-scurf.org>
+#
+# This file is part of NetSurf, http://www.netsurf-browser.org/
+#
+# NetSurf is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# NetSurf is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""
+Monkey Farmer
+
+The monkey farmer is a wrapper around `nsmonkey` which can be used to simplify
+access to the monkey behaviours and ultimately to write useful tests in an
+expressive but not overcomplicated DSLish way. Tests are, ultimately, still
+Python code.
+
+"""
+
+import asyncore
+import os
+import socket
+import subprocess
+import time
+
+class MonkeyFarmer(asyncore.dispatcher):
+ def __init__(self, monkey_cmd, online, quiet=False):
+ (mine, monkeys) = socket.socketpair()
+
+ asyncore.dispatcher.__init__(self, sock=mine)
+
+ self.monkey = subprocess.Popen(
+ monkey_cmd,
+ stdin=monkeys,
+ stdout=monkeys,
+ close_fds=[mine])
+
+ monkeys.close()
+
+ self.buffer = b""
+ self.incoming = b""
+ self.lines = []
+ self.scheduled = []
+ self.deadmonkey = False
+ self.online = online
+ self.quiet = quiet
+ self.discussion = []
+
+ def handle_connect(self):
+ pass
+
+ def handle_read(self):
+ got = self.recv(8192)
+ if not got:
+ self.deadmonkey = True
+ return
+ self.incoming += got
+ if b"\n" in self.incoming:
+ lines = self.incoming.split(b"\n")
+ self.incoming = lines.pop()
+ self.lines = lines
+
+ def writable(self):
+ return (len(self.buffer) > 0)
+
+ def handle_write(self):
+ sent = self.send(self.buffer)
+ self.buffer = self.buffer[sent:]
+
+ def tell_monkey(self, *args):
+ cmd = (" ".join(args))
+ if not self.quiet:
+ print(">>> {}".format(cmd))
+ self.discussion.append((">",cmd))
+ cmd = cmd + "\n"
+ self.buffer += cmd.encode('utf-8')
+
+ def monkey_says(self, line):
+ line = line.decode('utf-8')
+ if not self.quiet:
+ print("<<< {}".format(line))
+ self.discussion.append(("<", line))
+ self.online(line)
+
+ def schedule_event(self, event, secs=None, when=None):
+ assert(secs is not None or when is not None)
+ if when is None:
+ when = time.time() + secs
+ self.scheduled.append((when, event))
+ self.scheduled.sort(lambda a,b: cmp(a[0],b[0]))
+
+ def unschedule_event(self, event):
+ self.scheduled = [x for x in self.scheduled if x[1] != event]
+
+ def loop(self, once=False):
+ if len(self.lines) > 0:
+ self.monkey_says(self.lines.pop(0))
+ if once:
+ return
+ while not self.deadmonkey:
+ now = time.time()
+ while len(self.scheduled) > 0 and now >= self.scheduled[0][0]:
+ func = self.scheduled[0][1]
+ self.scheduled.pop(0)
+ func(self)
+ now = time.time()
+ if len(self.scheduled) > 0:
+ next = self.scheduled[0][0]
+ asyncore.loop(timeout=next-now, count=1)
+ else:
+ asyncore.loop(count=1)
+ if len(self.lines) > 0:
+ self.monkey_says(self.lines.pop(0))
+ if once:
+ break
+
+class Browser:
+ def __init__(self, monkey_cmd=["./nsmonkey"], quiet=False):
+ self.farmer = MonkeyFarmer(monkey_cmd=monkey_cmd, online=self.on_monkey_line, quiet=quiet)
+ self.windows = {}
+ self.current_draw_target = None
+ self.started = False
+ self.stopped = False
+ self.launchurl = None
+ now = time.time()
+ while not self.started:
+ self.farmer.loop(once=True)
+ if (time.time() - now) > 1:
+ break
+
+ def pass_options(self, *opts):
+ if len(opts) > 0:
+ self.farmer.tell_monkey("OPTIONS " + (" ".join(opts)))
+
+ def on_monkey_line(self, line):
+ parts = line.split(" ")
+ handler = getattr(self, "handle_" + parts[0], None)
+ if handler is not None:
+ handler(*parts[1:])
+
+ def quit(self):
+ self.farmer.tell_monkey("QUIT")
+
+ def quit_and_wait(self):
+ self.quit()
+ self.farmer.loop()
+ return self.stopped
+
+ def handle_GENERIC(self, what, *args):
+ if what == 'STARTED':
+ self.started = True
+ elif what == 'FINISHED':
+ self.stopped = True
+ elif what == 'LAUNCH':
+ self.launchurl = args[1]
+ else:
+ # TODO: Nothing for now?
+ pass
+
+ def handle_WINDOW(self, action, _win, winid, *args):
+ if action == "NEW":
+ new_win = BrowserWindow(self, winid, *args)
+ self.windows[winid] = new_win
+ else:
+ win = self.windows.get(winid, None)
+ if win is None:
+ print(" Unknown window id {}".format(winid))
+ else:
+ win.handle(action, *args)
+
+ def handle_PLOT(self, *args):
+ if self.current_draw_target is not None:
+ self.current_draw_target.handle_plot(*args)
+
+ def new_window(self, url=None):
+ if url is None:
+ self.farmer.tell_monkey("WINDOW NEW")
+ else:
+ self.farmer.tell_monkey("WINDOW NEW %s" % url)
+ wins_known = set(self.windows.keys())
+ while len(set(self.windows.keys()).difference(wins_known)) == 0:
+ self.farmer.loop(once=True)
+ poss_wins = set(self.windows.keys()).difference(wins_known)
+ return self.windows[poss_wins.pop()]
+
+
+class BrowserWindow:
+ def __init__(self, browser, winid, _for, coreid, _existing, otherid, _newtab, newtab, _clone, clone):
+ self.alive = True
+ self.browser = browser
+ self.winid = winid
+ self.coreid = coreid
+ self.existing = browser.windows.get(otherid, None)
+ self.newtab = newtab == "TRUE"
+ self.clone = clone == "TRUE"
+ self.width = 0
+ self.height = 0
+ self.title = ""
+ self.throbbing = False
+ self.scrollx = 0
+ self.scrolly = 0
+ self.content_width = 0
+ self.content_height = 0
+ self.status = ""
+ self.pointer = ""
+ self.scale = 1.0
+ self.url = ""
+ self.plotted = []
+ self.plotting = False
+
+ def kill(self):
+ self.browser.farmer.tell_monkey("WINDOW DESTROY %s" % self.winid)
+
+ def wait_until_dead(self, timeout=1):
+ now = time.time()
+ while self.alive:
+ self.browser.farmer.loop(once=True)
+ if (time.time() - now) > timeout:
+ break
+
+ def go(self, url, referer = None):
+ if referer is None:
+ self.browser.farmer.tell_monkey("WINDOW GO %s %s" % (
+ self.winid, url))
+ else:
+ self.browser.farmer.tell_monkey("WINDOW GO %s %s %s" % (
+ self.winid, url, referer))
+ self.wait_start_loading()
+
+ def reload(self):
+ self.browser.farmer.tell_monkey("WINDOW RELOAD %s" % self.winid)
+
+ def handle(self, action, *args):
+ handler = getattr(self, "handle_window_" + action, None)
+ if handler is not None:
+ handler(*args)
+
+ def handle_window_SIZE(self, _width, width, _height, height):
+ self.width = int(width)
+ self.height = int(height)
+
+ def handle_window_DESTROY(self):
+ self.alive = False
+
+ def handle_window_TITLE(self, _str, *title):
+ self.title = " ".join(title)
+
+ def handle_window_REDRAW(self):
+ pass
+
+ def handle_window_GET_DIMENSIONS(self, _width, width, _height, height):
+ self.width = width
+ self.height = height
+
+ def handle_window_NEW_CONTENT(self):
+ pass
+
+ def handle_window_NEW_ICON(self):
+ pass
+
+ def handle_window_START_THROBBER(self):
+ self.throbbing = True
+
+ def handle_window_STOP_THROBBER(self):
+ self.throbbing = False
+
+ def handle_window_SET_SCROLL(self, _x, x, _y, y):
+ self.scrollx = int(x)
+ self.scrolly = int(y)
+
+ def handle_window_UPDATE_BOX(self, _x, x, _y, y, _width, width, _height, height):
+ x = int(x)
+ y = int(y)
+ width = int(width)
+ height = int(height)
+ pass
+
+ def handle_window_UPDATE_EXTENT(self, _width, width, _height, height):
+ self.content_width = int(width)
+ self.content_height = int(height)
+
+ def handle_window_SET_STATUS(self, _str, *status):
+ self.status = (" ".join(status))
+
+ def handle_window_SET_POINTER(self, _ptr, ptr):
+ self.pointer = ptr
+
+ def handle_window_SET_SCALE(self, _scale, scale):
+ self.scale = float(scale)
+
+ def handle_window_SET_URL(self, _url, url):
+ self.url = url
+
+ def handle_window_GET_SCROLL(self, _x, x, _y, y):
+ self.scrollx = int(x)
+ self.scrolly = int(y)
+
+ def handle_window_SCROLL_START(self):
+ self.scrollx = 0
+ self.scrolly = 0
+
+ def handle_window_REDRAW(self, act):
+ if act == "START":
+ self.browser.current_draw_target = self
+ self.plotted = []
+ self.plotting = True
+ else:
+ self.browser.current_draw_target = None
+ self.plotting = False
+
+ def load_page(self, url=None, referer=None):
+ if url is not None:
+ self.go(url, referer)
+ self.wait_loaded()
+
+ def wait_start_loading(self):
+ while not self.throbbing:
+ self.browser.farmer.loop(once=True)
+
+ def wait_loaded(self):
+ self.wait_start_loading()
+ while self.throbbing:
+ self.browser.farmer.loop(once=True)
+
+ def handle_plot(self, *args):
+ self.plotted.append(args)
+
+ def redraw(self, coords=None):
+ if coords is None:
+ self.browser.farmer.tell_monkey("WINDOW REDRAW %s" % self.winid)
+ else:
+ self.browser.farmer.tell_monkey("WINDOW REDRAW %s %s" % (
+ self.winid, (" ".join(coords))))
+ while not self.plotting:
+ self.browser.farmer.loop(once=True)
+ while self.plotting:
+ self.browser.farmer.loop(once=True)
+ return self.plotted
+
+
+if __name__ == '__main__':
+ # Simple test is as follows...
+
+ browser = Browser(quiet=True)
+ win = browser.new_window()
+
+ fname = "test/js/inline-doc-write-simple.html"
+ full_fname = os.path.join(os.getcwd(), fname)
+
+ browser.pass_options("--enable_javascript=0")
+ win.load_page("file://" + full_fname)
+
+ print("Loaded, URL is {}".format(win.url))
+
+ cmds = win.redraw()
+ print("Received {} plot commands".format(len(cmds)))
+ for cmd in cmds:
+ if cmd[0] == "TEXT":
+ x = cmd[2]
+ y = cmd[4]
+ rest = " ".join(cmd[6:])
+ print("{} {} -> {}".format(x,y,rest))
+
+
+ browser.pass_options("--enable_javascript=1")
+ win.load_page("file://" + full_fname)
+
+ print("Loaded, URL is {}".format(win.url))
+
+ cmds = win.redraw()
+ print("Received {} plot commands".format(len(cmds)))
+ for cmd in cmds:
+ if cmd[0] == "TEXT":
+ x = cmd[2]
+ y = cmd[4]
+ rest = " ".join(cmd[6:])
+ print("{} {} -> {}".format(x,y,rest))
+
+ browser.quit_and_wait()
+
+ #print("Discussion was:")
+ #for line in browser.farmer.discussion:
+ # print("{} {}".format(line[0], line[1]))