summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/.gitignore1
-rw-r--r--test/Makefile28
-rw-r--r--test/assert.c19
-rw-r--r--test/corestrings.c12
-rw-r--r--test/data/Choices5
-rw-r--r--test/data/Choices-all13
-rw-r--r--test/data/urldb-out8
-rw-r--r--test/hashmap.c571
-rw-r--r--test/hashtable.c2
-rw-r--r--test/js/class-list.html29
-rw-r--r--test/js/event-onclick-insert.html18
-rw-r--r--test/js/index.html3
-rw-r--r--test/js/inserted-script-async.js1
-rw-r--r--test/js/inserted-script-defer.js1
-rw-r--r--test/js/inserted-script.html39
-rw-r--r--test/js/inserted-script.js1
-rw-r--r--test/js/life.html175
-rw-r--r--test/js/mandelbrot.html31
-rw-r--r--test/js/settimeout.html17
-rw-r--r--test/js/sleepy-async.html13
-rw-r--r--test/messages.c3
-rwxr-xr-xtest/monkey-see-monkey-do143
-rw-r--r--test/monkey-tests/401login.yaml38
-rw-r--r--test/monkey-tests/cache-test.yaml35
-rw-r--r--test/monkey-tests/inserted-script.yaml28
-rw-r--r--test/monkey-tests/quit-mid-fetch.yaml22
-rw-r--r--test/monkey-tests/resource-scheme.yaml34
-rw-r--r--test/monkey-tests/simultanious-fetches.yaml35
-rw-r--r--test/monkey-tests/sslcert.yaml33
-rw-r--r--test/monkey-tests/start-stop-no-js.yaml7
-rw-r--r--test/monkey-tests/start-stop.yaml6
-rw-r--r--test/monkey-tests/state-test.yaml69
-rwxr-xr-xtest/monkey_driver.py670
-rw-r--r--test/monkeyfarmer.py661
-rw-r--r--test/nsoption.c10
-rw-r--r--test/nsurl.c26
-rw-r--r--test/utils.c33
37 files changed, 2787 insertions, 53 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/Makefile b/test/Makefile
index 64c3f39a4..82ffee6fa 100644
--- a/test/Makefile
+++ b/test/Makefile
@@ -7,6 +7,7 @@ TESTS := \
nsoption \
bloom \
hashtable \
+ hashmap \
urlescape \
utils \
messages \
@@ -18,17 +19,19 @@ TESTS := \
NSURL_SOURCES := utils/nsurl/nsurl.c utils/nsurl/parse.c utils/idna.c \
utils/punycode.c
-# nsurl sources
+# nsurl test sources
nsurl_SRCS := $(NSURL_SOURCES) utils/corestrings.c test/log.c test/nsurl.c
# url database test sources
urldbtest_SRCS := $(NSURL_SOURCES) \
utils/bloom.c utils/nsoption.c utils/corestrings.c utils/time.c \
utils/hashtable.c utils/messages.c utils/utils.c \
+ utils/http/primitives.c utils/http/generics.c \
+ utils/http/strict-transport-security.c \
content/urldb.c \
test/log.c test/urldbtest.c
-# low level cache sources
+# low level cache test sources
llcache_SRCS := content/fetch.c content/fetchers/curl.c \
content/fetchers/about.c content/fetchers/data.c \
content/fetchers/resource.c content/llcache.c \
@@ -50,6 +53,10 @@ bloom_SRCS := utils/bloom.c test/bloom.c
# hash table test sources
hashtable_SRCS := utils/hashtable.c test/log.c test/hashtable.c
+# hashmap test sources
+hashmap_SRCS := $(NSURL_SOURCES) utils/hashmap.c utils/corestrings.c test/log.c test/hashmap.c
+hashmap_LD := -lmalloc_fig
+
# url escape test sources
urlescape_SRCS := utils/url.c test/log.c test/urlescape.c
@@ -125,20 +132,25 @@ endef
$(eval $(call pkg_cfg_detect_lib,check,Check))
-COMMON_WARNFLAGS = -W -Wall -Wundef -Wpointer-arith -Wcast-align \
+TEST_WARNFLAGS = -W -Wall -Wundef -Wpointer-arith -Wcast-align \
-Wwrite-strings -Wmissing-declarations -Wuninitialized
ifneq ($(CC_MAJOR),2)
- COMMON_WARNFLAGS += -Wno-unused-parameter
+ TEST_WARNFLAGS += -Wno-unused-parameter
endif
BASE_TESTCFLAGS := -std=c99 -g \
- $(COMMON_WARNFLAGS) \
+ $(TEST_WARNFLAGS) \
-D_DEFAULT_SOURCE \
-D_POSIX_C_SOURCE=200809L \
-D_XOPEN_SOURCE=600 \
-Itest -Iinclude -Icontent/handlers -Ifrontends -I. -I.. \
-Dnsgtk \
+ -DNETSURF_BUILTIN_LOG_FILTER=\"level:WARNING\" \
+ -DNETSURF_BUILTIN_VERBOSE_FILTER=\"level:DEBUG\" \
+ -DTESTROOT=\"$(TESTROOT)\" \
+ -DWITH_UTF8PROC \
+ $(SAN_FLAGS) \
$(shell pkg-config --cflags libcurl libparserutils libwapcaplet libdom libnsutils libutf8proc) \
$(LIB_CFLAGS)
TESTCFLAGS := $(BASE_TESTCFLAGS) \
@@ -147,12 +159,13 @@ TESTCFLAGS := $(BASE_TESTCFLAGS) \
TESTLDFLAGS := -L$(TESTROOT) \
$(shell pkg-config --libs libcurl libparserutils libwapcaplet libdom libnsutils libutf8proc) -lz \
+ $(SAN_FLAGS) \
$(LIB_LDFLAGS)\
$(COV_LDFLAGS)
# malloc faliure injection generator
$(TESTROOT)/libmalloc_fig.so:test/malloc_fig.c
- $(CC) -shared -fPIC -I. -std=c99 $(COMMON_WARNFLAGS) $^ -o $@
+ $(CC) -shared -fPIC -I. -std=c99 $(TEST_WARNFLAGS) $^ -ldl -o $@
# Source files for all tests being compiled
TESTSOURCES :=
@@ -200,11 +213,12 @@ $(eval $(foreach SOURCE,$(sort $(filter %.c,$(NOCOV_TESTSOURCES))), \
$(call compile_test_nocov_target_c,$(SOURCE),$(subst /,_,$(SOURCE:.c=.o)),$(subst /,_,$(SOURCE:.c=.d)))))
-.PHONY:test coverage
+.PHONY:test coverage sanitize
test: $(TESTROOT)/created $(TESTROOT)/libmalloc_fig.so $(addsuffix _test,$(TESTS))
coverage: test
+sanitize: test
$(TESTROOT)/created:
$(VQ)echo " MKDIR: $(TESTROOT)"
diff --git a/test/assert.c b/test/assert.c
index d21926e5e..fb4db8cc9 100644
--- a/test/assert.c
+++ b/test/assert.c
@@ -30,7 +30,23 @@ __ns_assert_fail(const char *__assertion, const char *__file,
unsigned int __line, const char *__function)
__THROW __attribute__ ((__noreturn__));
-/* We use this to flush coverage data */
+#if __GNUC__ > 10
+
+/* We use this to dump coverage data in gcc 11 and later */
+extern void __gcov_dump(void);
+
+/* And here's our entry point */
+void
+__ns_assert_fail(const char *__assertion, const char *__file,
+ unsigned int __line, const char *__function)
+{
+ __gcov_dump();
+ __assert_fail(__assertion, __file, __line, __function);
+}
+
+#else
+
+/* We use this to flush coverage data before gcc 11 */
extern void __gcov_flush(void);
/* And here's our entry point */
@@ -41,3 +57,4 @@ __ns_assert_fail(const char *__assertion, const char *__file,
__gcov_flush();
__assert_fail(__assertion, __file, __line, __function);
}
+#endif
diff --git a/test/corestrings.c b/test/corestrings.c
index 02640c953..c3c4e93eb 100644
--- a/test/corestrings.c
+++ b/test/corestrings.c
@@ -40,7 +40,7 @@
*
* This is used to test all the out of memory paths in initialisation.
*/
-#define CORESTRING_TEST_COUNT 435
+#define CORESTRING_TEST_COUNT 488
START_TEST(corestrings_test)
{
@@ -53,8 +53,12 @@ START_TEST(corestrings_test)
res = corestrings_fini();
malloc_limit(UINT_MAX);
-
- ck_assert_int_eq(ires, NSERROR_NOMEM);
+
+ if (_i < CORESTRING_TEST_COUNT) {
+ ck_assert_int_eq(ires, NSERROR_NOMEM);
+ } else {
+ ck_assert_int_eq(ires, NSERROR_OK);
+ }
ck_assert_int_eq(res, NSERROR_OK);
}
END_TEST
@@ -65,7 +69,7 @@ static TCase *corestrings_case_create(void)
TCase *tc;
tc = tcase_create("corestrings");
- tcase_add_loop_test(tc, corestrings_test, 0, CORESTRING_TEST_COUNT);
+ tcase_add_loop_test(tc, corestrings_test, 0, CORESTRING_TEST_COUNT + 1);
return tc;
}
diff --git a/test/data/Choices b/test/data/Choices
index bd946f77b..511ecf87e 100644
--- a/test/data/Choices
+++ b/test/data/Choices
@@ -30,7 +30,6 @@ disc_cache_size:1073741824
disc_cache_age:28
block_advertisements:0
do_not_track:0
-minimum_gif_delay:10
send_referer:1
foreground_images:1
background_images:1
@@ -50,8 +49,6 @@ window_x:0
window_y:0
window_width:0
window_height:0
-window_screen_width:0
-window_screen_height:0
toolbar_status_size:6667
scale:100
incremental_reflow:1
@@ -101,7 +98,6 @@ sys_colour_ThreeDShadow:000000
sys_colour_Window:000000
sys_colour_WindowFrame:000000
sys_colour_WindowText:000000
-render_resample:1
downloads_clear:0
request_overwrite:1
downloads_directory:/home/vince
@@ -109,7 +105,6 @@ url_file:/home/vince/.netsurf/URLs
show_single_tab:1
button_type:1
disable_popups:0
-disable_plugins:0
history_age:0
hover_urls:0
focus_new:0
diff --git a/test/data/Choices-all b/test/data/Choices-all
index 9f2e18377..5c26f2887 100644
--- a/test/data/Choices-all
+++ b/test/data/Choices-all
@@ -16,16 +16,17 @@ font_fantasy:Serif
accept_language:en
accept_charset:
memory_cache_size:12582912
+disc_cache_path:
disc_cache_size:1073741824
disc_cache_age:28
block_advertisements:0
do_not_track:0
-minimum_gif_delay:10
send_referer:1
foreground_images:1
background_images:1
animate_images:1
enable_javascript:1
+author_level_css:1
script_timeout:10
expire_url:28
font_default:0
@@ -41,8 +42,6 @@ window_x:0
window_y:0
window_width:0
window_height:0
-window_screen_width:0
-window_screen_height:0
toolbar_status_size:6667
scale:100
incremental_reflow:1
@@ -67,6 +66,7 @@ remove_backgrounds:0
enable_loosening:1
enable_PDF_compression:1
enable_PDF_password:0
+prefer_dark_mode:0
sys_colour_ActiveBorder:d3d3d3
sys_colour_ActiveCaption:f1f1f1
sys_colour_AppWorkspace:f1f1f1
@@ -96,8 +96,7 @@ sys_colour_Window:f1f1f1
sys_colour_WindowFrame:4e4e4e
sys_colour_WindowText:000000
log_filter:level:WARNING
-verbose_filter:level:VERBOSE
-render_resample:1
+verbose_filter:level:DEBUG
downloads_clear:0
request_overwrite:1
downloads_directory:/home/vince
@@ -105,7 +104,6 @@ url_file:/home/vince/.netsurf/URLs
show_single_tab:1
button_type:1
disable_popups:0
-disable_plugins:0
history_age:0
hover_urls:0
focus_new:0
@@ -113,4 +111,5 @@ new_blank:0
hotlist_path:/home/vince/.netsurf/Hotlist
developer_view:0
position_tab:0
-toolbar_order:
+toolbar_items:
+bar_show:
diff --git a/test/data/urldb-out b/test/data/urldb-out
index 6db02bc91..11f400e02 100644
--- a/test/data/urldb-out
+++ b/test/data/urldb-out
@@ -1,5 +1,5 @@
-106
-en.wikipedia.org
+107
+en.wikipedia.org 0 0
1
https
@@ -9,7 +9,7 @@ https
1
Wikipedia, the free encyclopedia
-slashdot.org
+slashdot.org 0 0
2
http
@@ -27,7 +27,7 @@ https
1
Slashdot: News for nerds, stuff that matters
-www.bbc.co.uk
+www.bbc.co.uk 0 0
1
http
diff --git a/test/hashmap.c b/test/hashmap.c
new file mode 100644
index 000000000..ed951e9a7
--- /dev/null
+++ b/test/hashmap.c
@@ -0,0 +1,571 @@
+/*
+ * Copyright 2020 Daniel Silverstone <dsilvers@netsurf-browser.org>
+ * Copyright 2016 Vincent Sanders <vince@netsurf-browser.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/>.
+ */
+
+/**
+ * \file
+ * Tests for hashmap.
+ *
+ * In part, borrows from the corestrings tests
+ */
+
+#include "utils/config.h"
+
+#include <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <check.h>
+#include <limits.h>
+
+#include <libwapcaplet/libwapcaplet.h>
+
+#include "utils/nsurl.h"
+#include "utils/corestrings.h"
+#include "utils/hashmap.h"
+
+#include "test/malloc_fig.h"
+
+/* Low level fixtures */
+
+static void
+corestring_create(void)
+{
+ ck_assert(corestrings_init() == NSERROR_OK);
+}
+
+/**
+ * iterator for any remaining strings in teardown fixture
+ */
+static void
+netsurf_lwc_iterator(lwc_string *str, void *pw)
+{
+ fprintf(stderr,
+ "[%3u] %.*s",
+ str->refcnt,
+ (int)lwc_string_length(str),
+ lwc_string_data(str));
+}
+
+static void
+corestring_teardown(void)
+{
+ corestrings_fini();
+
+ lwc_iterate_strings(netsurf_lwc_iterator, NULL);
+}
+
+/* Infra */
+
+static ssize_t keys;
+static ssize_t values;
+
+typedef struct {
+ nsurl *key;
+} hashmap_test_value_t;
+
+static void *
+key_clone(void *key)
+{
+ /* Pretend cloning costs memory so that it can fail for
+ * testing error return pathways
+ */
+ void *temp = malloc(1);
+ if (temp == NULL) return NULL;
+ free(temp);
+ /* In reality we just ref the nsurl */
+ keys++;
+ return nsurl_ref((nsurl *)key);
+}
+
+static void
+key_destroy(void *key)
+{
+ keys--;
+ nsurl_unref((nsurl *)key);
+}
+
+static uint32_t
+key_hash(void *key)
+{
+ /* Deliberately bad hash.
+ * returns 0, 1, 2, or 3 to force bucket chaining
+ */
+ return nsurl_hash((nsurl *)key) & 3;
+}
+
+static bool
+key_eq(void *key1, void *key2)
+{
+ return nsurl_compare((nsurl *)key1, (nsurl*)key2, NSURL_COMPLETE);
+}
+
+static void *
+value_alloc(void *key)
+{
+ hashmap_test_value_t *ret = malloc(sizeof(hashmap_test_value_t));
+
+ if (ret == NULL)
+ return NULL;
+
+ ret->key = (nsurl *)key;
+
+ values++;
+
+ return ret;
+}
+
+static void
+value_destroy(void *value)
+{
+ hashmap_test_value_t *val = value;
+
+ /* Do nothing for now */
+
+ free(val);
+ values--;
+}
+
+static hashmap_parameters_t test_params = {
+ .key_clone = key_clone,
+ .key_hash = key_hash,
+ .key_eq = key_eq,
+ .key_destroy = key_destroy,
+ .value_alloc = value_alloc,
+ .value_destroy = value_destroy,
+};
+
+/* Iteration helpers */
+
+static size_t iteration_counter = 0;
+static size_t iteration_stop = 0;
+static char iteration_ctx = 0;
+
+static bool
+hashmap_test_iterator_cb(void *key, void *value, void *ctx)
+{
+ ck_assert(ctx == &iteration_ctx);
+ iteration_counter++;
+ return iteration_counter == iteration_stop;
+}
+
+/* Fixtures for basic tests */
+
+static hashmap_t *test_hashmap = NULL;
+
+static void
+basic_fixture_create(void)
+{
+ corestring_create();
+
+ test_hashmap = hashmap_create(&test_params);
+
+ ck_assert(test_hashmap != NULL);
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+}
+
+static void
+basic_fixture_teardown(void)
+{
+ hashmap_destroy(test_hashmap);
+ test_hashmap = NULL;
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+
+ corestring_teardown();
+}
+
+/* basic api tests */
+
+START_TEST(empty_hashmap_create_destroy)
+{
+ ck_assert_int_eq(hashmap_count(test_hashmap), 0);
+}
+END_TEST
+
+START_TEST(check_not_present)
+{
+ /* We're checking for a key which should not be present */
+ ck_assert(hashmap_lookup(test_hashmap, corestring_nsurl_about_blank) == NULL);
+}
+END_TEST
+
+START_TEST(insert_works)
+{
+ hashmap_test_value_t *value = hashmap_insert(test_hashmap, corestring_nsurl_about_blank);
+ ck_assert(value != NULL);
+ ck_assert(value->key == corestring_nsurl_about_blank);
+ ck_assert_int_eq(hashmap_count(test_hashmap), 1);
+}
+END_TEST
+
+START_TEST(remove_not_present)
+{
+ ck_assert(hashmap_remove(test_hashmap, corestring_nsurl_about_blank) == false);
+}
+END_TEST
+
+START_TEST(insert_then_remove)
+{
+ hashmap_test_value_t *value = hashmap_insert(test_hashmap, corestring_nsurl_about_blank);
+ ck_assert(value != NULL);
+ ck_assert(value->key == corestring_nsurl_about_blank);
+ ck_assert_int_eq(keys, 1);
+ ck_assert_int_eq(values, 1);
+ ck_assert_int_eq(hashmap_count(test_hashmap), 1);
+ ck_assert(hashmap_remove(test_hashmap, corestring_nsurl_about_blank) == true);
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+ ck_assert_int_eq(hashmap_count(test_hashmap), 0);
+}
+END_TEST
+
+START_TEST(insert_then_lookup)
+{
+ hashmap_test_value_t *value = hashmap_insert(test_hashmap, corestring_nsurl_about_blank);
+ ck_assert(value != NULL);
+ ck_assert(value->key == corestring_nsurl_about_blank);
+ ck_assert(hashmap_lookup(test_hashmap, corestring_nsurl_about_blank) == value);
+}
+END_TEST
+
+START_TEST(iterate_empty)
+{
+ iteration_stop = iteration_counter = 0;
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == false);
+ ck_assert_int_eq(iteration_counter, 0);
+}
+END_TEST
+
+START_TEST(iterate_one)
+{
+ iteration_stop = iteration_counter = 0;
+ hashmap_test_value_t *value = hashmap_insert(test_hashmap, corestring_nsurl_about_blank);
+ ck_assert(value != NULL);
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == false);
+ ck_assert_int_eq(iteration_counter, 1);
+}
+END_TEST
+
+START_TEST(iterate_one_and_stop)
+{
+ iteration_stop = 1;
+ iteration_counter = 0;
+ hashmap_test_value_t *value = hashmap_insert(test_hashmap, corestring_nsurl_about_blank);
+ ck_assert(value != NULL);
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == true);
+ ck_assert_int_eq(iteration_counter, 1);
+}
+END_TEST
+
+static TCase *basic_api_case_create(void)
+{
+ TCase *tc;
+ tc = tcase_create("Basic API");
+
+ tcase_add_unchecked_fixture(tc,
+ basic_fixture_create,
+ basic_fixture_teardown);
+
+ tcase_add_test(tc, empty_hashmap_create_destroy);
+ tcase_add_test(tc, check_not_present);
+ tcase_add_test(tc, insert_works);
+ tcase_add_test(tc, remove_not_present);
+ tcase_add_test(tc, insert_then_remove);
+ tcase_add_test(tc, insert_then_lookup);
+
+ tcase_add_test(tc, iterate_empty);
+ tcase_add_test(tc, iterate_one);
+ tcase_add_test(tc, iterate_one_and_stop);
+
+ return tc;
+}
+
+/* Chain verification test suite */
+
+typedef struct {
+ const char *url;
+ nsurl *nsurl;
+} case_pair;
+
+/* The hobbled hash has only 4 values
+ * By having at least 12 test cases, we can be confident that
+ * at worst they'll all be on one chain, but at best there'll
+ * be four chains of 3 entries which means we should be able
+ * to validate prevptr and next in all cases.
+ */
+static case_pair chain_pairs[] = {
+ { "https://www.google.com/", NULL },
+ { "https://www.google.co.uk/", NULL },
+ { "https://www.netsurf-browser.org/", NULL },
+ { "http://www.google.com/", NULL },
+ { "http://www.google.co.uk/", NULL },
+ { "http://www.netsurf-browser.org/", NULL },
+ { "file:///tmp/test.html", NULL },
+ { "file:///tmp/inner.html", NULL },
+ { "about:blank", NULL },
+ { "about:welcome", NULL },
+ { "about:testament", NULL },
+ { "resources:default.css", NULL },
+ { NULL, NULL }
+};
+
+static void
+chain_fixture_create(void)
+{
+ case_pair *chain_case = chain_pairs;
+ basic_fixture_create();
+
+ while (chain_case->url != NULL) {
+ ck_assert(nsurl_create(chain_case->url, &chain_case->nsurl) == NSERROR_OK);
+ chain_case++;
+ }
+
+}
+
+static void
+chain_fixture_teardown(void)
+{
+ case_pair *chain_case = chain_pairs;
+
+ while (chain_case->url != NULL) {
+ nsurl_unref(chain_case->nsurl);
+ chain_case->nsurl = NULL;
+ chain_case++;
+ }
+
+ basic_fixture_teardown();
+}
+
+START_TEST(chain_add_remove_all)
+{
+ case_pair *chain_case;
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) == NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) != NULL);
+ ck_assert(hashmap_remove(test_hashmap, chain_case->nsurl) == true);
+ }
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+}
+END_TEST
+
+START_TEST(chain_add_all_remove_all)
+{
+ case_pair *chain_case;
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) == NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ }
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_remove(test_hashmap, chain_case->nsurl) == true);
+ }
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+}
+END_TEST
+
+START_TEST(chain_add_all_twice_remove_all)
+{
+ case_pair *chain_case;
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) == NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ }
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) != NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ }
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_remove(test_hashmap, chain_case->nsurl) == true);
+ }
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+}
+END_TEST
+
+START_TEST(chain_add_all_twice_remove_all_iterate)
+{
+ case_pair *chain_case;
+ size_t chain_count = 0;
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) == NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ chain_count++;
+ }
+
+ iteration_counter = 0;
+ iteration_stop = 0;
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == false);
+ ck_assert_int_eq(iteration_counter, chain_count);
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_lookup(test_hashmap, chain_case->nsurl) != NULL);
+ ck_assert(hashmap_insert(test_hashmap, chain_case->nsurl) != NULL);
+ }
+
+ iteration_counter = 0;
+ iteration_stop = 0;
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == false);
+ ck_assert_int_eq(iteration_counter, chain_count);
+ ck_assert_int_eq(hashmap_count(test_hashmap), chain_count);
+
+ iteration_counter = 0;
+ iteration_stop = chain_count;
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == true);
+ ck_assert_int_eq(iteration_counter, chain_count);
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ ck_assert(hashmap_remove(test_hashmap, chain_case->nsurl) == true);
+ }
+
+ iteration_counter = 0;
+ iteration_stop = chain_count;
+ ck_assert(hashmap_iterate(test_hashmap, hashmap_test_iterator_cb, &iteration_ctx) == false);
+ ck_assert_int_eq(iteration_counter, 0);
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+ ck_assert_int_eq(hashmap_count(test_hashmap), 0);
+}
+END_TEST
+
+#define CHAIN_TEST_MALLOC_COUNT_MAX 60
+
+START_TEST(chain_add_all_remove_all_alloc)
+{
+ bool failed = false;
+ case_pair *chain_case;
+
+ malloc_limit(_i);
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ if (hashmap_insert(test_hashmap, chain_case->nsurl) == NULL) {
+ failed = true;
+ }
+ }
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ if (hashmap_insert(test_hashmap, chain_case->nsurl) == NULL) {
+ failed = true;
+ }
+ }
+
+ for (chain_case = chain_pairs;
+ chain_case->url != NULL;
+ chain_case++) {
+ hashmap_remove(test_hashmap, chain_case->nsurl);
+ }
+
+ malloc_limit(UINT_MAX);
+
+ ck_assert_int_eq(keys, 0);
+ ck_assert_int_eq(values, 0);
+
+ if (_i < CHAIN_TEST_MALLOC_COUNT_MAX) {
+ ck_assert(failed);
+ } else {
+ ck_assert(!failed);
+ }
+
+}
+END_TEST
+
+static TCase *chain_case_create(void)
+{
+ TCase *tc;
+ tc = tcase_create("Bucket Chain tests");
+
+ tcase_add_unchecked_fixture(tc,
+ chain_fixture_create,
+ chain_fixture_teardown);
+
+ tcase_add_test(tc, chain_add_remove_all);
+ tcase_add_test(tc, chain_add_all_remove_all);
+ tcase_add_test(tc, chain_add_all_twice_remove_all);
+ tcase_add_test(tc, chain_add_all_twice_remove_all_iterate);
+
+ tcase_add_loop_test(tc, chain_add_all_remove_all_alloc, 0, CHAIN_TEST_MALLOC_COUNT_MAX + 1);
+
+ return tc;
+}
+
+/*
+ * hashmap test suite creation
+ */
+static Suite *hashmap_suite_create(void)
+{
+ Suite *s;
+ s = suite_create("Hashmap");
+
+ suite_add_tcase(s, basic_api_case_create());
+ suite_add_tcase(s, chain_case_create());
+
+ return s;
+}
+
+int main(int argc, char **argv)
+{
+ int number_failed;
+ SRunner *sr;
+
+ sr = srunner_create(hashmap_suite_create());
+
+ srunner_run_all(sr, CK_ENV);
+
+ number_failed = srunner_ntests_failed(sr);
+ srunner_free(sr);
+
+ return (number_failed == 0) ? EXIT_SUCCESS : EXIT_FAILURE;
+}
diff --git a/test/hashtable.c b/test/hashtable.c
index 11c58c625..ea74b78b2 100644
--- a/test/hashtable.c
+++ b/test/hashtable.c
@@ -25,11 +25,13 @@
*/
#include <assert.h>
+#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <check.h>
+#include "utils/errors.h"
#include "utils/hashtable.h"
/* Limit for hash table tests which use /usr/share/dict/words */
diff --git a/test/js/class-list.html b/test/js/class-list.html
new file mode 100644
index 000000000..4c73283e5
--- /dev/null
+++ b/test/js/class-list.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <title>Class List (and other token lists?)</title>
+ <style>
+ .bad { background-color: red; }
+ .ok { background-color: green; }
+ </style>
+ </head>
+ <body>
+ <h1>This is a set of demonstrators for the token list Element.classList</h1>
+ <h2>This first is taken from the MDN for DOMTokenList</h2>
+ <span id="demo1" class=" d d e f bad"></span>
+ <script>
+ var span = document.getElementById("demo1");
+ var classes = span.classList;
+ classes.add("x", "d", "g");
+ classes.remove("e", "g");
+ classes.toggle("d"); // Toggles d off
+ classes.toggle("q", false); // Forces q off (won't be present)
+ classes.toggle("d"); // Toggles d on
+ classes.toggle("d", true); // Forces d on (won't toggle it off again)
+ if (classes.contains("d")) {
+ classes.add("ok")
+ classes.remove("bad")
+ span.textContent = "span classList is \"" + classes + '"';
+ }
+ </script>
+ </body>
+</html>
diff --git a/test/js/event-onclick-insert.html b/test/js/event-onclick-insert.html
new file mode 100644
index 000000000..62b9d7ee8
--- /dev/null
+++ b/test/js/event-onclick-insert.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<body>
+
+<button onclick="add_paragraph()">Click me!</button>
+
+<script>
+function add_paragraph() {
+ var paragraph = document.createElement("P");
+ var textnode = document.createTextNode("New paragraph!");
+ paragraph.appendChild(textnode);
+ document.body.appendChild(paragraph);
+}
+</script>
+
+</body>
+</html>
+
diff --git a/test/js/index.html b/test/js/index.html
index 2abe954e5..6d2c6541e 100644
--- a/test/js/index.html
+++ b/test/js/index.html
@@ -104,6 +104,9 @@
<li><a href="assorted-log-doc-write.html">console.log and document.write</a></li>
<li><a href="wikipedia-lcm.html">Example from wikipedia</a></li>
<li><a href="verify-instanceofness.html">Check instanceof behaviour</a></li>
+<li><a href="class-list.html">Class list (and other token lists?)</a></li>
+<li><a href="mandelbrot.html">Canvas/ImageData Mandelbrot ploter</a></li>
+<li><a href="life.html">Game of Life</a></li>
</ul>
</body>
diff --git a/test/js/inserted-script-async.js b/test/js/inserted-script-async.js
new file mode 100644
index 000000000..aa6c0a351
--- /dev/null
+++ b/test/js/inserted-script-async.js
@@ -0,0 +1 @@
+console.log("External %s dynamism!", "asynchronous");
diff --git a/test/js/inserted-script-defer.js b/test/js/inserted-script-defer.js
new file mode 100644
index 000000000..2d89edd34
--- /dev/null
+++ b/test/js/inserted-script-defer.js
@@ -0,0 +1 @@
+console.log("External deferred dynamism!");
diff --git a/test/js/inserted-script.html b/test/js/inserted-script.html
new file mode 100644
index 000000000..b1c381aaa
--- /dev/null
+++ b/test/js/inserted-script.html
@@ -0,0 +1,39 @@
+<html>
+ <head>
+ <title>Inserted script test</title>
+ <script>
+ /* After one second, insert an inline script element */
+ setTimeout(function() {
+ var div = document.createElement("DIV");
+ var script = document.createElement("SCRIPT");
+ var textnode = document.createTextNode("console.log(\"Dynamism\");");
+ script.appendChild(textnode);
+ div.appendChild(script);
+ document.body.appendChild(div);
+ }, 1000);
+ /* After two seconds, insert a script element for immediate fetch */
+ setTimeout(function() {
+ var script = document.createElement("SCRIPT");
+ script.setAttribute("src", "inserted-script.js");
+ document.body.appendChild(script);
+ }, 2000);
+ /* After three seconds, insert a script element for async fetch */
+ setTimeout(function() {
+ var script = document.createElement("SCRIPT");
+ script.setAttribute("src", "inserted-script-async.js");
+ script.setAttribute("async", "");
+ document.body.appendChild(script);
+ }, 3000);
+ /* After four seconds, insert a script element for deferred fetch */
+ setTimeout(function() {
+ var script = document.createElement("SCRIPT");
+ script.setAttribute("src", "inserted-script-defer.js");
+ script.setAttribute("defer", "");
+ document.body.appendChild(script);
+ }, 4000);
+ </script>
+ </head>
+ <body>
+ Check the log
+ </body>
+</html>
diff --git a/test/js/inserted-script.js b/test/js/inserted-script.js
new file mode 100644
index 000000000..f3a954827
--- /dev/null
+++ b/test/js/inserted-script.js
@@ -0,0 +1 @@
+console.log("External dynamism!");
diff --git a/test/js/life.html b/test/js/life.html
new file mode 100644
index 000000000..de54d0aae
--- /dev/null
+++ b/test/js/life.html
@@ -0,0 +1,175 @@
+<html>
+ <head>
+ <meta charset="UTF-8" />
+ <title>Conway's Game of Life</title>
+ <link rel="stylesheet" type="text/css" href="resource:internal.css" />
+ <style>
+ canvas#surface {
+ width: 50vmin;
+ height: 50vmin;
+ border: 2px solid black;
+ }
+ </style>
+ </head>
+ <body class="ns-even-bg ns-even-fg ns-border">
+ <h1 class="ns-border">Conway's Game of Life</h1>
+ <div style="margin: 1em;">
+ <div>
+ Run: <input id="running" type="checkbox" checked/><br />
+ Set Size: <input id="width" type="text" size="4" value="50" /> x
+ <input id="height" type="text" size="4" value="50" />
+ <button id="commitsize">Commit</button><br />
+ </div>
+ <div>
+ <canvas id="surface" width="50" height="50">
+ Sorry, you can't play Game of Life if JavaScript is turned off
+ </canvas>
+ </div>
+ <div>
+ <button id="random">Randomise</button>
+ </div>
+ </div>
+ </body>
+ <script>
+ (function () {
+ const running = document.getElementById("running");
+ const iwidth = document.getElementById("width");
+ const iheight = document.getElementById("height");
+ const surface = document.getElementById("surface");
+ const context = surface.getContext("2d");
+ var width = surface.width - 10;
+ var height = surface.height - 10;
+ var frame = context.createImageData(width, height);
+ var drawto = context.createImageData(width, height);
+ var greyto = context.createImageData(width, height);
+ const greylevel = 31;
+
+ function getOffset(x, y) {
+ if (x < 0) {
+ x = width + x;
+ }
+ if (y < 0) {
+ y = height + y;
+ }
+ if (x >= width) {
+ x = x - width;
+ }
+ if (y >= height) {
+ y = y - height;
+ }
+ return (y * width + x) * 4;
+ }
+ function getCell(x, y) {
+ const offset = getOffset(x, y);
+ return frame.data[offset + 3] != 0;
+ }
+ function setCell(x, y) {
+ const offset = getOffset(x, y);
+ drawto.data[offset + 3] = 255;
+ greyto.data[offset + 3] = greylevel;
+ }
+ function clearCell(x, y) {
+ const offset = getOffset(x, y);
+ drawto.data[offset + 3] = 0;
+ greyto.data[offset + 3] = 0;
+ }
+ function countNeighbours(x, y) {
+ return (
+ getCell(x - 1, y - 1) +
+ getCell(x, y - 1) +
+ getCell(x + 1, y - 1) +
+ getCell(x - 1, y) +
+ getCell(x + 1, y) +
+ getCell(x - 1, y + 1) +
+ getCell(x, y + 1) +
+ getCell(x + 1, y + 1)
+ );
+ }
+ function flip() {
+ var temp = frame;
+ context.putImageData(drawto, 5, 5);
+ context.putImageData(greyto, 5 - width, 5 - height); /* top left */
+ context.putImageData(greyto, 5 - width, 5); /* left */
+ context.putImageData(greyto, 5, 5 - height); /* top */
+ context.putImageData(greyto, 5 + width, 5 + height); /* bottom right */
+ context.putImageData(greyto, 5 + width, 5); /* right */
+ context.putImageData(greyto, 5, 5 + height); /* bottom */
+ context.putImageData(greyto, 5 + width, 5 - height); /* top right */
+ context.putImageData(greyto, 5 - width, 5 + height); /* bottom left */
+ frame = drawto;
+ drawto = temp;
+ }
+ /* Game of life is run on a timer */
+ setInterval(function () {
+ if (!running.checked) {
+ return;
+ }
+ console.log("Frame");
+ /* To do a frame of GoL we compute by consuming frame and writing to drawto */
+ for (var y = 0; y < height; y++) {
+ for (var x = 0; x < width; x++) {
+ const neighbours = countNeighbours(x, y);
+ if (getCell(x, y)) {
+ if (neighbours == 2 || neighbours == 3) {
+ setCell(x, y); // live, 2/3 neigh => stay alive
+ } else {
+ clearCell(x, y); // live, <2/>3 neigh => dies
+ }
+ } else {
+ if (neighbours == 3) {
+ setCell(x, y); // dead, 3 neigh => born
+ } else {
+ clearCell(x, y); // dead, !3 neigh => stay dead
+ }
+ }
+ }
+ }
+ flip();
+ }, 100);
+ const randomise = function () {
+ var ofs = 3;
+ for (var y = 0; y < height; y++) {
+ for (var x = 0; x < width; x++) {
+ if (Math.random() < 0.5) {
+ drawto.data[ofs] = 0;
+ } else {
+ drawto.data[ofs] = 255;
+ greyto.data[ofs] = greylevel;
+ }
+ ofs += 4;
+ }
+ }
+ flip();
+ };
+ document.getElementById("random").addEventListener("click", randomise);
+ document
+ .getElementById("commitsize")
+ .addEventListener("click", function () {
+ const iwval = parseInt(iwidth.value, 10);
+ const ihval = parseInt(iheight.value, 10);
+ console.log(width, height, "->", iwval, ihval);
+ if (
+ (iwval != width || ihval != height) &&
+ iwval >= 10 &&
+ iwval <= 200 &&
+ ihval >= 10 &&
+ ihval <= 200
+ ) {
+ console.log("yes");
+ surface.height = ihval + 10;
+ context.height = ihval + 10;
+ height = ihval;
+ surface.width = iwval + 10;
+ context.width = iwval + 10;
+ width = iwval;
+ frame = context.createImageData(width, height);
+ drawto = context.createImageData(width, height);
+ greyto = context.createImageData(width, height);
+ resetGrey();
+ randomise();
+ }
+ });
+ randomise();
+ })();
+ </script>
+</html>
diff --git a/test/js/mandelbrot.html b/test/js/mandelbrot.html
new file mode 100644
index 000000000..38f77eff5
--- /dev/null
+++ b/test/js/mandelbrot.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>JS Mandelbrot</title>
+ <script src="https://nerget.com/mandelbrot.js"></script>
+ <script>
+ var drawn = false;
+ var dimension = 2;
+ var cx = -dimension / 2 + 0.5;
+ var cy = -dimension / 2;
+
+ function log(msg) {
+ document.getElementById("log").innerHTML += msg + "<br/>";
+ }
+
+ function draw() {
+ var forceSlowPath = document.getElementById('forceSlowPath').checked;
+ drawMandelbrot(document.getElementById('canvas').getContext('2d'), 200, 200,
+ cx + dimension / 2, cy + dimension / 2, dimension, 500, forceSlowPath);
+ drawn = true;
+ }
+
+ </script>
+ </head>
+ <body>
+ <canvas id="canvas" width="200" height="200" style="border: 1px solid black;"></canvas>
+ <br />
+ <input id="forceSlowPath" type="checkbox">Use slow path.</input> <br />
+ <a href="javascript:draw()">Start</a>
+ <div id="log"></div>
+ </body>
+</html>
diff --git a/test/js/settimeout.html b/test/js/settimeout.html
new file mode 100644
index 000000000..1755973c6
--- /dev/null
+++ b/test/js/settimeout.html
@@ -0,0 +1,17 @@
+<html>
+ <head>
+ <title>setTimeout and setInterval</title>
+ <script>
+ var counter = 0;
+ var interval_handle = setInterval(function() {
+ console.log("Called back ", counter, " times");
+ counter = counter + 1;
+ }, 100);
+ setTimeout(function() {clearInterval(interval_handle);}, 10000);
+ </script>
+ </head>
+ <body>
+ Check the log, it should be printing a callback indicator for ten
+ seconds and then stop.
+ </body>
+</html>
diff --git a/test/js/sleepy-async.html b/test/js/sleepy-async.html
new file mode 100644
index 000000000..b94997f05
--- /dev/null
+++ b/test/js/sleepy-async.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <title>
+ Async sleepy script
+ </title>
+ </head>
+ <body>
+ This page is loading a sleepy async script.
+
+ Do not expect it to do anything useful.
+ <script src="https://test.netsurf-browser.org/cgi-bin/sleep.cgi" type="text/javascript" async></script>
+ </body>
+</html>
diff --git a/test/messages.c b/test/messages.c
index ae82d1ede..3ec770a56 100644
--- a/test/messages.c
+++ b/test/messages.c
@@ -118,8 +118,7 @@ START_TEST(message_get_buff_test)
ck_assert_int_eq(res, NSERROR_OK);
buf = messages_get_buff("DefinitelyNotAKey");
- ck_assert_str_eq(buf, "DefinitelyNotAKey");
- free(buf);
+ ck_assert(buf == NULL);
buf = messages_get_buff("NoMemory");
ck_assert_str_eq(buf, "NetSurf is running out of memory. Please free some memory and try again.");
diff --git a/test/monkey-see-monkey-do b/test/monkey-see-monkey-do
new file mode 100755
index 000000000..72b8685ec
--- /dev/null
+++ b/test/monkey-see-monkey-do
@@ -0,0 +1,143 @@
+#!/usr/bin/python3
+
+'''
+NetSurf automated test runner
+
+This script retrives a test plan from the NetSurf infrastructure and
+ executes it using the monkey frontend
+'''
+
+# If you have any poo, fling it now!
+
+import sys
+import getopt
+import multiprocessing as mp
+from urllib import request, parse
+from io import StringIO
+import yaml
+import monkey_driver as driver
+
+# Otherwise let's begin...
+
+BASE_PATH = "https://test.netsurf-browser.org/cgi-bin/monkey-index.cgi"
+MONKEY_PATH = "./nsmonkey"
+
+mp.set_start_method('fork')
+
+def decode_trace_line(l):
+ from re import findall, match
+ from subprocess import getstatusoutput
+
+ caps = findall(r'./nsmonkey\(\+(0x[0-9a-f]+)\)', l);
+ if not caps:
+ return l
+
+ exitcode, output = getstatusoutput(
+ "addr2line -e {} -a -p -f -C {} 2>/dev/null".format(
+ MONKEY_PATH, caps[0]))
+ if exitcode != 0:
+ return './nsmonkey(+{})'.format(caps[0])
+
+ m = match(r'0x(.+): (.+) at (.+):(.+)', output)
+
+ return '{}:{}({})[0x{}]'.format(
+ m.group(3), m.group(4), m.group(2), m.group(1))
+
+def decode_trace(s):
+ return "\n".join(decode_trace_line(l) for l in s.split("\n"))
+
+def child_run_test(verbose, parts):
+ outcapture = StringIO()
+ errcapture = StringIO()
+ oldout = sys.stdout
+ olderr = sys.stderr
+ sys.stdout = outcapture
+ sys.stderr = errcapture
+ try:
+ driver.run_preloaded_test(MONKEY_PATH, parts)
+ except:
+ sys.stdout = oldout
+ sys.stderr = olderr
+ print("FAIL:")
+ print("STDOUT:\n{}\n".format(outcapture.getvalue()))
+ print("STDERR:\n{}\n".format(decode_trace(errcapture.getvalue())))
+ print("RERAISE:")
+ raise
+ else:
+ sys.stdout = oldout
+ sys.stderr = olderr
+ if verbose:
+ print("STDOUT:\n{}\n".format(outcapture.getvalue()))
+
+def run_test(verbose, parts):
+ p = mp.Process(target=child_run_test, args=(verbose, parts, ))
+ p.start()
+ p.join()
+ return p.exitcode
+
+def print_usage():
+ print('Usage:')
+ print(' ' + sys.argv[0] + ' [-v] [-h] [-d <division>] [-g group]')
+
+def parse_argv(argv):
+ verbose = False
+ division = None
+ group = None
+ try:
+ opts, args = getopt.getopt(argv, "hvd:g:", [])
+ except getopt.GetoptError:
+ print_usage()
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print_usage()
+ sys.exit()
+ elif opt in ("-v", "--verbose"):
+ verbose = True
+ elif opt == '-d':
+ division = arg
+ elif opt == '-g':
+ group = arg
+
+ return verbose, division, group
+
+def main():
+ verbose, division, group = parse_argv(sys.argv[1:])
+
+ print("Fetching tests...")
+ data_dict = {}
+ if division is not None:
+ data_dict['division'] = division
+ if group is not None:
+ data_dict['group'] = group
+
+ data = parse.urlencode(data_dict).encode()
+ req = request.Request(BASE_PATH, data=data)
+ index = request.urlopen(req)
+ index = index.read()
+
+ print("Parsing tests...")
+ test_set = yaml.load_all(index, Loader=yaml.SafeLoader)
+
+ print("Running tests...")
+ ret = 0
+ for test in test_set:
+ if test["kind"] == 'group':
+ print("Start group: {}".format(test["group"]))
+ print(" [ {} ]".format(test["description"]))
+ elif test["kind"] == 'test':
+ print(" => Run test: {}".format(test["filename"]))
+ ret = run_test(verbose, test["content"])
+ if ret != 0:
+ break
+
+ if ret != 0:
+ print("FAIL")
+ sys.exit(1)
+ else:
+ print("PASS")
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/test/monkey-tests/401login.yaml b/test/monkey-tests/401login.yaml
new file mode 100644
index 000000000..a9a74cd10
--- /dev/null
+++ b/test/monkey-tests/401login.yaml
@@ -0,0 +1,38 @@
+title: Test the 401 LOGIN functionality
+group: real-world
+steps:
+- action: launch
+ language: en
+- action: window-new
+ tag: win1
+- action: navigate
+ window: win1
+ url: https://httpbin.org/basic-auth/foo/bar
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - text-not-contains: "\"authenticated\": true"
+- action: add-auth
+ url: https://httpbin.org/basic-auth/foo/bar
+ realm: Fake Realm
+ username: foo
+ password: bar
+- action: navigate
+ window: win1
+ url: https://httpbin.org/basic-auth/foo/bar
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - text-contains: "\"authenticated\": true"
+- action: window-close
+ window: win1
+- action: quit
+
diff --git a/test/monkey-tests/cache-test.yaml b/test/monkey-tests/cache-test.yaml
new file mode 100644
index 000000000..372c5a1ba
--- /dev/null
+++ b/test/monkey-tests/cache-test.yaml
@@ -0,0 +1,35 @@
+title: cache test
+group: performance
+steps:
+- action: launch
+ language: en
+- action: timer-start
+ timer: timer1
+- action: window-new
+ tag: win1
+- action: navigate
+ window: win1
+ url: http://www.bbc.co.uk/news
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: timer-stop
+ timer: timer1
+- action: timer-start
+ timer: timer2
+- action: window-new
+ tag: win2
+- action: navigate
+ window: win2
+ url: http://www.bbc.co.uk/news
+- action: block
+ conditions:
+ - window: win2
+ status: complete
+- action: timer-stop
+ timer: timer2
+- action: timer-check
+ condition: timer2 < timer1
+- action: quit
+
diff --git a/test/monkey-tests/inserted-script.yaml b/test/monkey-tests/inserted-script.yaml
new file mode 100644
index 000000000..ac7bb0f7f
--- /dev/null
+++ b/test/monkey-tests/inserted-script.yaml
@@ -0,0 +1,28 @@
+title: run inserted-script test in JS enabled browser
+group: basic
+steps:
+- action: launch
+ args:
+ - "--enable_javascript=1"
+- action: window-new
+ tag: win1
+- action: clear-log
+ window: win1
+- action: navigate
+ window: win1
+ url: about:blank
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: js-exec
+ window: win1
+ cmd: location.assign("file:///home/dsilvers/dev-netsurf/workspace/netsurf/test/js/inserted-script.html")
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: wait-log
+ window: win1
+ substring: deferred
+- action: quit
diff --git a/test/monkey-tests/quit-mid-fetch.yaml b/test/monkey-tests/quit-mid-fetch.yaml
new file mode 100644
index 000000000..cffdae3f2
--- /dev/null
+++ b/test/monkey-tests/quit-mid-fetch.yaml
@@ -0,0 +1,22 @@
+title: quitting mid-fetch
+group: cleanup
+steps:
+- action: repeat
+ min: 0
+ step: 50
+ tag: sleepytimer
+ steps:
+ - action: launch
+ - action: window-new
+ tag: win1
+ - action: navigate
+ window: win1
+ url: http://www.bbc.co.uk/news
+ - action: sleep-ms
+ time: sleepytimer
+ conditions:
+ - window: win1
+ status: complete
+ breaks: sleepytimer
+ - action: quit
+
diff --git a/test/monkey-tests/resource-scheme.yaml b/test/monkey-tests/resource-scheme.yaml
new file mode 100644
index 000000000..791a79cd6
--- /dev/null
+++ b/test/monkey-tests/resource-scheme.yaml
@@ -0,0 +1,34 @@
+title: resource scheme
+group: basic
+steps:
+- action: launch
+ language: en
+- action: window-new
+ tag: win1
+- action: navigate
+ window: win1
+ url: resource:does-not-exist
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - text-contains: Not found
+ - text-contains: Error 404
+- action: navigate
+ window: win1
+ url: resource:netsurf.png
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - bitmap-count: 1
+- action: window-close
+ window: win1
+- action: quit
+
diff --git a/test/monkey-tests/simultanious-fetches.yaml b/test/monkey-tests/simultanious-fetches.yaml
new file mode 100644
index 000000000..57ec6e17f
--- /dev/null
+++ b/test/monkey-tests/simultanious-fetches.yaml
@@ -0,0 +1,35 @@
+title: simultanious page fetches
+group: real-world
+steps:
+- action: launch
+ language: en
+- action: window-new
+ tag: win1
+- action: window-new
+ tag: win2
+- action: window-new
+ tag: win3
+- action: window-new
+ tag: win4
+- action: navigate
+ window: win1
+ url: http://www.bbc.co.uk/news
+- action: navigate
+ window: win2
+ url: http://www.amazon.co.uk/
+- action: navigate
+ window: win3
+ url: http://www.theregister.co.uk/
+- action: navigate
+ window: win4
+ url: http://www.arstechnica.co.uk/
+- action: block
+ conditions:
+ - window: "*all*"
+ status: complete
+- action: window-close
+ window: win1
+- action: window-close
+ window: win2
+- action: quit
+
diff --git a/test/monkey-tests/sslcert.yaml b/test/monkey-tests/sslcert.yaml
new file mode 100644
index 000000000..96df2d651
--- /dev/null
+++ b/test/monkey-tests/sslcert.yaml
@@ -0,0 +1,33 @@
+title: Test the SSL certificate error functionality
+group: real-world
+steps:
+- action: launch
+ language: en
+- action: window-new
+ tag: win1
+- action: navigate
+ window: win1
+ url: https://badssl.com/
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - text-contains: "badssl.com"
+- action: navigate
+ window: win1
+ url: https://expired.badssl.com/
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: plot-check
+ window: win1
+ checks:
+ - text-not-contains: "expired. badssl.com"
+- action: window-close
+ window: win1
+- action: quit
+
diff --git a/test/monkey-tests/start-stop-no-js.yaml b/test/monkey-tests/start-stop-no-js.yaml
new file mode 100644
index 000000000..028e08f8e
--- /dev/null
+++ b/test/monkey-tests/start-stop-no-js.yaml
@@ -0,0 +1,7 @@
+title: start and stop browser without JS
+group: initial
+steps:
+- action: launch
+ options:
+ - enable_javascript=0
+- action: quit
diff --git a/test/monkey-tests/start-stop.yaml b/test/monkey-tests/start-stop.yaml
new file mode 100644
index 000000000..68df47316
--- /dev/null
+++ b/test/monkey-tests/start-stop.yaml
@@ -0,0 +1,6 @@
+title: start and stop browser
+group: basic
+steps:
+- action: launch
+- action: quit
+
diff --git a/test/monkey-tests/state-test.yaml b/test/monkey-tests/state-test.yaml
new file mode 100644
index 000000000..6f25a78d4
--- /dev/null
+++ b/test/monkey-tests/state-test.yaml
@@ -0,0 +1,69 @@
+title: Page state info test
+group: basic
+steps:
+- action: launch
+ language: en
+- action: window-new
+ tag: win1
+- action: navigate
+ window: win1
+ url: about:config
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: INTERNAL
+- action: navigate
+ window: win1
+ url: file:///
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: LOCAL
+- action: navigate
+ window: win1
+ url: http://test.netsurf-browser.org/html/trivial-document.html
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: INSECURE
+- action: navigate
+ window: win1
+ url: https://test.netsurf-browser.org/html/trivial-document.html
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: SECURE
+- action: navigate
+ window: win1
+ url: https://test.netsurf-browser.org/html/trivial-document-with-png.html
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: SECURE
+- action: navigate
+ window: win1
+ url: https://test.netsurf-browser.org/html/trivial-document-with-http-png.html
+- action: block
+ conditions:
+ - window: win1
+ status: complete
+- action: page-info-state
+ window: win1
+ match: SECURE_ISSUES
+- action: quit
+
diff --git a/test/monkey_driver.py b/test/monkey_driver.py
new file mode 100755
index 000000000..9b810d2a6
--- /dev/null
+++ b/test/monkey_driver.py
@@ -0,0 +1,670 @@
+#!/usr/bin/python3
+#
+# Copyright 2019 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/>.
+
+"""
+runs tests in monkey as defined in a yaml file
+"""
+
+# pylint: disable=locally-disabled, missing-docstring
+
+import os
+import sys
+import getopt
+import time
+import yaml
+
+from monkeyfarmer import Browser
+
+
+class DriverBrowser(Browser):
+ def __init__(self, *args, **kwargs):
+ super(DriverBrowser, self).__init__(*args, **kwargs)
+ self.auth = []
+
+ def add_auth(self, url, realm, username, password):
+ self.auth.append((url, realm, username, password))
+
+ def remove_auth(self, url, realm, username, password):
+ keep = []
+
+ def matches(first, second):
+ if first is None or second is None:
+ return True
+ return first == second
+
+ for (iurl, irealm, iusername, ipassword) in self.auth:
+ if not (matches(url, iurl) or
+ matches(realm, irealm) or
+ matches(username, iusername) or
+ matches(password, ipassword)):
+ keep.append((iurl, irealm, iusername, ipassword))
+ self.auth = keep
+
+ def handle_ready_login(self, logwin):
+ # We have logwin.{url,username,password,realm}
+ # We must logwin.send_{username,password}(xxx)
+ # We may logwin.go()
+ # We may logwin.destroy()
+ def matches(first, second):
+ if first is None or second is None:
+ return True
+ return first == second
+ candidates = []
+ for (url, realm, username, password) in self.auth:
+ score = 0
+ if matches(url, logwin.url):
+ score += 1
+ if matches(realm, logwin.realm):
+ score += 1
+ if matches(username, logwin.username):
+ score += 1
+ if score > 0:
+ candidates.append((score, username, password))
+ if candidates:
+ candidates.sort()
+ (score, username, password) = candidates[-1]
+ print("401: Found candidate {}/{} with score {}".format(username, password, score))
+ logwin.send_username(username)
+ logwin.send_password(password)
+ logwin.go()
+ else:
+ print("401: No candidate found, cancelling login box")
+ logwin.destroy()
+
+
+def print_usage():
+ print('Usage:')
+ print(' ' + sys.argv[0] + ' -m <path to monkey> -t <path to test> [-w <wrapper arguments>]')
+
+
+def parse_argv(argv):
+
+ # pylint: disable=locally-disabled, unused-variable
+
+ path_monkey = ''
+ path_test = ''
+ wrapper = None
+ try:
+ opts, args = getopt.getopt(argv, "hm:t:w:", ["monkey=", "test=", "wrapper="])
+ except getopt.GetoptError:
+ print_usage()
+ sys.exit(2)
+ for opt, arg in opts:
+ if opt == '-h':
+ print_usage()
+ sys.exit()
+ elif opt in ("-m", "--monkey"):
+ path_monkey = arg
+ elif opt in ("-t", "--test"):
+ path_test = arg
+ elif opt in ("-w", "--wrapper"):
+ if wrapper is None:
+ wrapper = []
+ wrapper.extend(arg.split())
+
+ if path_monkey == '':
+ print_usage()
+ sys.exit()
+ if path_test == '':
+ print_usage()
+ sys.exit()
+
+ return path_monkey, path_test, wrapper
+
+
+def load_test_plan(path):
+
+ # pylint: disable=locally-disabled, broad-except
+
+ plan = []
+ with open(path, 'r') as stream:
+ try:
+ plan = (yaml.load(stream, Loader=yaml.CSafeLoader))
+ except Exception as exc:
+ print(exc)
+ return plan
+
+
+def get_indent(ctx):
+ return ' ' * ctx["depth"]
+
+
+def print_test_plan_info(ctx, plan):
+
+ # pylint: disable=locally-disabled, unused-argument
+
+ print('Running test: [' + plan["group"] + '] ' + plan["title"])
+
+
+def assert_browser(ctx):
+ assert ctx['browser'].started
+ assert not ctx['browser'].stopped
+
+
+def conds_met(ctx, conds):
+ # for each condition listed determine if they have been met
+ # efectively this is condition1 | condition2
+ for cond in conds:
+ if 'timer' in cond.keys():
+ timer = cond['timer']
+ elapsed = cond['elapsed']
+ assert_browser(ctx)
+ assert ctx['timers'].get(timer) is not None
+ taken = time.time() - ctx['timers'][timer]["start"]
+ if taken >= elapsed:
+ return True
+ elif 'window' in cond.keys():
+ status = cond['status']
+ window = cond['window']
+ assert status == "complete" or status == "loading" # TODO: Add more status support?
+ if window == "*all*":
+ # all windows must be complete, or any still loading
+ throbbing = False
+ for win in ctx['windows'].items():
+ if win[1].throbbing:
+ throbbing = True
+ # throbbing and want loading => true
+ # not throbbing and want complete => true
+ if (status == "loading") == throbbing:
+ return True
+ else:
+ win = ctx['windows'][window]
+ if win.throbbing == (status == "loading"):
+ return True
+ else:
+ raise AssertionError("Unknown condition: {}".format(repr(cond)))
+
+ return False
+
+
+def run_test_step_action_launch(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+
+ # ensure browser is not already launched
+ assert ctx.get('browser') is None
+ assert ctx.get('windows') is None
+
+ # build command line switches list
+ monkey_cmd = [ctx["monkey"]]
+ for option in step.get('launch-options', []):
+ monkey_cmd.append("--{}".format(option))
+ print(get_indent(ctx) + " " + "Command line: " + repr(monkey_cmd))
+
+ # build command environment
+ monkey_env = os.environ.copy()
+ for envkey, envvalue in step.get('environment', {}).items():
+ monkey_env[envkey] = envvalue
+ print(get_indent(ctx) + " " + envkey + "=" + envvalue)
+ if 'language' in step.keys():
+ monkey_env['LANGUAGE'] = step['language']
+
+ # create browser object
+ ctx['browser'] = DriverBrowser(
+ monkey_cmd=monkey_cmd,
+ monkey_env=monkey_env,
+ quiet=True,
+ wrapper=ctx.get("wrapper"))
+ assert_browser(ctx)
+ ctx['windows'] = dict()
+
+ # set user options
+ for option in step.get('options', []):
+ print(get_indent(ctx) + " " + option)
+ ctx['browser'].pass_options(option)
+
+
+def run_test_step_action_window_new(ctx, step):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ 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):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ 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)
+ timeout = int(step.get('timeout', 30))
+ win.kill()
+ win.wait_until_dead(timeout=timeout)
+ assert not win.alive
+
+
+def run_test_step_action_navigate(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ if 'url' in step.keys():
+ url = step['url']
+ elif 'repeaturl' in step.keys():
+ repeat = ctx['repeats'].get(step['repeaturl'])
+ assert repeat is not None
+ assert repeat.get('values') is not None
+ url = repeat['values'][repeat['i']]
+ else:
+ url = None
+ assert url is not None
+ tag = step['window']
+ print(get_indent(ctx) + " " + tag + " --> " + url)
+ win = ctx['windows'].get(tag)
+ assert win is not None
+ win.go(url)
+
+
+def run_test_step_action_stop(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.stop()
+
+
+def run_test_step_action_reload(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.reload()
+
+
+def run_test_step_action_sleep_ms(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ conds = step.get('conditions', {})
+ sleep_time = step['time']
+ sleep = 0
+ have_repeat = False
+ if isinstance(sleep_time, str):
+ assert ctx['repeats'].get(sleep_time) is not None
+ repeat = ctx['repeats'].get(sleep_time)
+ sleep = repeat["i"] / 1000
+ start = repeat["start"]
+ have_repeat = True
+ else:
+ sleep = sleep_time / 1000
+ start = time.time()
+
+ while True:
+ slept = time.time() - start
+ if conds_met(ctx, conds):
+ if have_repeat:
+ ctx['repeats'][sleep_time]["loop"] = False
+ print(get_indent(ctx) + " Condition met after {}s".format(slept))
+ break
+ elif slept > sleep:
+ print(get_indent(ctx) + " Condition not met after {}s".format(sleep))
+ break
+ else:
+ ctx['browser'].farmer.loop(once=True)
+
+
+def run_test_step_action_block(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ conds = step['conditions']
+ assert_browser(ctx)
+
+ while not conds_met(ctx, conds):
+ ctx['browser'].farmer.loop(once=True)
+
+
+def run_test_step_action_repeat(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ tag = step['tag']
+ assert ctx['repeats'].get(tag) is None
+ # initialise the loop continue conditional
+ ctx['repeats'][tag] = {"loop": True, }
+
+ if 'values' in step.keys():
+ # value iterator
+ ctx['repeats'][tag]['values'] = step["values"]
+ ctx['repeats'][tag]["max"] = len(step["values"])
+ ctx['repeats'][tag]["i"] = 0
+ ctx['repeats'][tag]["step"] = 1
+ else:
+ # numeric iterator
+ ctx['repeats'][tag]['values'] = None
+
+ if 'min' in step.keys():
+ ctx['repeats'][tag]["i"] = step["min"]
+ else:
+ ctx['repeats'][tag]["i"] = 0
+
+ if 'step' in step.keys():
+ ctx['repeats'][tag]["step"] = step["step"]
+ else:
+ ctx['repeats'][tag]["step"] = 1
+
+ if 'max' in step.keys():
+ ctx['repeats'][tag]["max"] = step["max"]
+ else:
+ ctx['repeats'][tag]["max"] = None
+
+ while ctx['repeats'][tag]["loop"]:
+ ctx['repeats'][tag]["start"] = time.time()
+ ctx["depth"] += 1
+
+ # run through steps for this iteration
+ for stp in step["steps"]:
+ run_test_step(ctx, stp)
+
+ # increment iterator
+ ctx['repeats'][tag]["i"] += ctx['repeats'][tag]["step"]
+
+ # check for end condition
+ if ctx['repeats'][tag]["max"] is not None:
+ if ctx['repeats'][tag]["i"] >= ctx['repeats'][tag]["max"]:
+ ctx['repeats'][tag]["loop"] = False
+
+ ctx["depth"] -= 1
+
+
+def run_test_step_action_click(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ win = ctx['windows'][step['window']]
+ targets = step['target']
+ if type(targets) == dict:
+ targets = [targets]
+ button = step.get('button', 'left').upper()
+ kind = step.get('kind', 'single').upper()
+ all_text_list = []
+ bitmaps = []
+ for plot in win.redraw():
+ if plot[0] == 'TEXT':
+ all_text_list.append((int(plot[2]), int(plot[4]), " ".join(plot[6:])))
+ if plot[0] == 'BITMAP':
+ bitmaps.append((int(plot[2]), int(plot[4]), int(plot[6]), int(plot[8])))
+
+ x = None
+ y = None
+
+ for target in targets:
+ if 'bitmap' in target:
+ if x is not None:
+ assert False, "Found more than one thing to click on, oh well"
+ bmap = int(target['bitmap'])
+ assert bmap < 0 or bmap >= len(bitmaps)
+ x = bitmaps[bmap][0] + bitmaps[bmap][2] / 2
+ y = bitmaps[bmap][1] + bitmaps[bmap][3] / 2
+ elif 'text' in target:
+ if x is not None:
+ assert False, "Found more than one thing to click on, oh well"
+ text = target['text']
+ for textentry in all_text_list:
+ if text in textentry[2]:
+ if x is not None:
+ assert False, "Text {} found more than once".format(text)
+ x = textentry[0] + 2
+ y = textentry[1] + 2
+
+ # Now we want to click on the x/y coordinate given
+ print(get_indent(ctx) + " Clicking at {}, {} (button={} kind={})".format(x, y, button, kind))
+ win.click(x, y, button, kind)
+
+
+def run_test_step_action_wait_loading(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ win = ctx['windows'][step['window']]
+ win.wait_start_loading()
+
+def run_test_step_action_plot_check(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ win = ctx['windows'][step['window']]
+
+ if 'area' in step.keys():
+ if step["area"] == "extent":
+ # ought to capture the extent updates and use that, instead use a
+ # big area and have the browser clip it
+ area=["0","0","1000","1000000"]
+ else:
+ area = [step["area"]]
+ else:
+ area = None
+
+ # get the list of checks
+ if 'checks' in step.keys():
+ checks = step['checks']
+ else:
+ checks = {}
+
+ all_text_list = []
+ bitmaps = []
+ for plot in win.redraw(coords=area):
+ if plot[0] == 'TEXT':
+ all_text_list.extend(plot[6:])
+ if plot[0] == 'BITMAP':
+ bitmaps.append(plot[1:])
+ all_text = " ".join(all_text_list)
+ 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 'text-not-contains' in check.keys():
+ print(" Check {} NOT in {}".format(repr(check['text-not-contains']), repr(all_text)))
+ assert check['text-not-contains'] not in all_text
+ elif 'bitmap-count' in check.keys():
+ print(" Check bitmap count is {}".format(int(check['bitmap-count'])))
+ assert len(bitmaps) == int(check['bitmap-count'])
+ else:
+ raise AssertionError("Unknown check: {}".format(repr(check)))
+
+
+def run_test_step_action_timer_start(ctx, step):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ print(get_indent(ctx) + "Action: " + step["action"])
+ tag = step['timer']
+ assert_browser(ctx)
+ assert ctx['timers'].get(tag) is None
+ ctx['timers'][tag] = {}
+ ctx['timers'][tag]["start"] = time.time()
+
+
+def run_test_step_action_timer_restart(ctx, step):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ print(get_indent(ctx) + "Action: " + step["action"])
+ timer = step['timer']
+ assert_browser(ctx)
+ assert ctx['timers'].get(timer) is not None
+ taken = time.time() - ctx['timers'][timer]["start"]
+ print("{} {} restarted at: {:.2f}s".format(get_indent(ctx), timer, taken))
+ ctx['timers'][timer]["taken"] = taken
+ ctx['timers'][timer]["start"] = time.time()
+
+
+def run_test_step_action_timer_stop(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ timer = step['timer']
+ assert_browser(ctx)
+ assert ctx['timers'].get(timer) is not None
+ taken = time.time() - ctx['timers'][timer]["start"]
+ print("{} {} took: {:.2f}s".format(get_indent(ctx), timer, taken))
+ ctx['timers'][timer]["taken"] = taken
+
+
+def run_test_step_action_timer_check(ctx, step):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ print(get_indent(ctx) + "Action: " + step["action"])
+ condition = step["condition"].split()
+ assert len(condition) == 3
+ timer1 = ctx['timers'].get(condition[0])
+ timer2 = ctx['timers'].get(condition[2])
+ assert timer1 is not None
+ assert timer2 is not None
+ assert timer1["taken"] is not None
+ assert timer2["taken"] is not None
+ assert condition[1] in ('<', '>')
+ if condition[1] == '<':
+ assert timer1["taken"] < timer2["taken"]
+ elif condition[1] == '>':
+ assert timer1["taken"] > timer2["taken"]
+
+
+def run_test_step_action_add_auth(ctx, step):
+ print(get_indent(ctx) + "Action:" + step["action"])
+ assert_browser(ctx)
+ browser = ctx['browser']
+ browser.add_auth(step.get("url"), step.get("realm"),
+ step.get("username"), step.get("password"))
+
+
+def run_test_step_action_remove_auth(ctx, step):
+
+ # pylint: disable=locally-disabled, invalid-name
+
+ print(get_indent(ctx) + "Action:" + step["action"])
+ assert_browser(ctx)
+ browser = ctx['browser']
+ browser.remove_auth(step.get("url"), step.get("realm"),
+ step.get("username"), step.get("password"))
+
+
+def run_test_step_action_clear_log(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ tag = step['window']
+ print(get_indent(ctx) + " " + tag + " Log cleared")
+ win = ctx['windows'].get(tag)
+ assert win is not None
+ win.clear_log()
+
+
+def run_test_step_action_wait_log(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ tag = step['window']
+ source = step.get('source')
+ foldable = step.get('foldable')
+ level = step.get('level')
+ substr = step.get('substring')
+ print(get_indent(ctx) + " " + tag + " Wait for logging")
+ win = ctx['windows'].get(tag)
+ assert win is not None
+ win.wait_for_log(source=source, foldable=foldable, level=level, substr=substr)
+
+
+def run_test_step_action_js_exec(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ tag = step['window']
+ cmd = step['cmd']
+ print(get_indent(ctx) + " " + tag + " Run " + cmd)
+ win = ctx['windows'].get(tag)
+ assert win is not None
+ win.js_exec(cmd)
+
+
+def run_test_step_action_page_info_state(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
+ match = step['match']
+ assert win.page_info_state == match
+
+
+def run_test_step_action_quit(ctx, step):
+ print(get_indent(ctx) + "Action: " + step["action"])
+ assert_browser(ctx)
+ browser = ctx.pop('browser')
+ assert browser.quit_and_wait()
+ # clean up context as all windows have gone away after browser quit
+ ctx.pop('windows')
+
+
+STEP_HANDLERS = {
+ "launch": run_test_step_action_launch,
+ "window-new": run_test_step_action_window_new,
+ "window-close": run_test_step_action_window_close,
+ "navigate": run_test_step_action_navigate,
+ "reload": run_test_step_action_reload,
+ "stop": run_test_step_action_stop,
+ "sleep-ms": run_test_step_action_sleep_ms,
+ "block": run_test_step_action_block,
+ "repeat": run_test_step_action_repeat,
+ "timer-start": run_test_step_action_timer_start,
+ "timer-restart": run_test_step_action_timer_restart,
+ "timer-stop": run_test_step_action_timer_stop,
+ "timer-check": run_test_step_action_timer_check,
+ "plot-check": run_test_step_action_plot_check,
+ "click": run_test_step_action_click,
+ "wait-loading": run_test_step_action_wait_loading,
+ "add-auth": run_test_step_action_add_auth,
+ "remove-auth": run_test_step_action_remove_auth,
+ "clear-log": run_test_step_action_clear_log,
+ "wait-log": run_test_step_action_wait_log,
+ "js-exec": run_test_step_action_js_exec,
+ "page-info-state":
+ run_test_step_action_page_info_state,
+ "quit": run_test_step_action_quit,
+}
+
+
+def run_test_step(ctx, step):
+ STEP_HANDLERS[step["action"]](ctx, step)
+
+
+def walk_test_plan(ctx, plan):
+ ctx["depth"] = 0
+ ctx["timers"] = dict()
+ ctx['repeats'] = dict()
+ for step in plan["steps"]:
+ run_test_step(ctx, step)
+
+
+def run_test_plan(ctx, plan):
+ print_test_plan_info(ctx, plan)
+ walk_test_plan(ctx, plan)
+
+
+def run_preloaded_test(path_monkey, plan):
+ ctx = {
+ "monkey": path_monkey,
+ }
+ run_test_plan(ctx, plan)
+
+
+def main(argv):
+ ctx = {}
+ path_monkey, path_test, wrapper = parse_argv(argv)
+ plan = load_test_plan(path_test)
+ ctx["monkey"] = path_monkey
+ ctx["wrapper"] = wrapper
+ run_test_plan(ctx, plan)
+
+
+# Some python weirdness to get to main().
+if __name__ == "__main__":
+ main(sys.argv[1:])
diff --git a/test/monkeyfarmer.py b/test/monkeyfarmer.py
new file mode 100644
index 000000000..905fd9a81
--- /dev/null
+++ b/test/monkeyfarmer.py
@@ -0,0 +1,661 @@
+# Copyright 2017-2019 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.
+
+"""
+
+# pylint: disable=locally-disabled, missing-docstring
+
+import asyncore
+import os
+import socket
+import subprocess
+import time
+import errno
+import sys
+
+class StderrEcho(asyncore.dispatcher):
+ def __init__(self, sockend):
+ asyncore.dispatcher.__init__(self, sock=sockend)
+ self.incoming = b""
+
+ def handle_connect(self):
+ pass
+
+ def handle_close(self):
+ # the pipe to the monkey process has closed
+ self.close()
+
+ def handle_read(self):
+ try:
+ got = self.recv(8192)
+ if not got:
+ return
+ except socket.error as error:
+ if error.errno == errno.EAGAIN or error.errno == errno.EWOULDBLOCK:
+ return
+ else:
+ raise
+
+ self.incoming += got
+ if b"\n" in self.incoming:
+ lines = self.incoming.split(b"\n")
+ self.incoming = lines.pop()
+ for line in lines:
+ try:
+ line = line.decode('utf-8')
+ except UnicodeDecodeError:
+ print("WARNING: Unicode decode error")
+ line = line.decode('utf-8', 'replace')
+
+ sys.stderr.write("{}\n".format(line))
+
+
+class MonkeyFarmer(asyncore.dispatcher):
+
+ # pylint: disable=locally-disabled, too-many-instance-attributes
+
+ def __init__(self, monkey_cmd, monkey_env, online, quiet=False, *, wrapper=None):
+ (mine, monkeys) = socket.socketpair()
+
+ asyncore.dispatcher.__init__(self, sock=mine)
+
+ (mine2, monkeyserr) = socket.socketpair()
+
+ self._errwrapper = StderrEcho(mine2)
+
+ if wrapper is not None:
+ new_cmd = list(wrapper)
+ new_cmd.extend(monkey_cmd)
+ monkey_cmd = new_cmd
+
+ self.monkey = subprocess.Popen(
+ monkey_cmd,
+ env=monkey_env,
+ stdin=monkeys,
+ stdout=monkeys,
+ stderr=monkeyserr,
+ close_fds=[mine, mine2])
+
+ monkeys.close()
+ monkeyserr.close()
+
+ self.buffer = b""
+ self.incoming = b""
+ self.lines = []
+ self.scheduled = []
+ self.deadmonkey = False
+ self.online = online
+ self.quiet = quiet
+ self.discussion = []
+ self.maybe_slower = wrapper is not None
+
+ def handle_connect(self):
+ pass
+
+ def handle_close(self):
+ # the pipe to the monkey process has closed
+ self.close()
+
+ def handle_read(self):
+ try:
+ got = self.recv(8192)
+ if not got:
+ self.deadmonkey = True
+ # ensure the child process is finished and report the exit
+ if self.monkey.poll() is None:
+ self.monkey.terminate()
+ self.monkey.wait()
+ print("Handling an exit {}".format(self.monkey.returncode))
+ print("The following are present in the queue: {}".format(self.lines))
+ self.lines.append("GENERIC EXIT {}".format(
+ self.monkey.returncode).encode('utf-8'))
+ print("The queue is now: {}".format(self.lines))
+ return
+ except socket.error as error:
+ if error.errno == errno.EAGAIN or error.errno == errno.EWOULDBLOCK:
+ return
+ else:
+ raise
+
+ self.incoming += got
+ if b"\n" in self.incoming:
+ lines = self.incoming.split(b"\n")
+ self.incoming = lines.pop()
+ self.lines.extend(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):
+ try:
+ line = line.decode('utf-8')
+ except UnicodeDecodeError:
+ print("WARNING: Unicode decode error")
+ line = line.decode('utf-8', 'replace')
+ 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()
+
+ 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_event = self.scheduled[0][0]
+ asyncore.loop(timeout=next_event - now, count=1)
+ else:
+ asyncore.loop(count=1)
+ while len(self.lines) > 0:
+ self.monkey_says(self.lines.pop(0))
+ if once or self.deadmonkey:
+ return
+
+
+class Browser:
+
+ # pylint: disable=locally-disabled, too-many-instance-attributes, dangerous-default-value, invalid-name
+
+ def __init__(self, monkey_cmd=["./nsmonkey"], monkey_env=None, quiet=False, *, wrapper=None):
+ self.farmer = MonkeyFarmer(
+ monkey_cmd=monkey_cmd,
+ monkey_env=monkey_env,
+ online=self.on_monkey_line,
+ quiet=quiet,
+ wrapper=wrapper)
+ self.windows = {}
+ self.logins = {}
+ self.current_draw_target = None
+ self.started = False
+ self.stopped = False
+ self.launchurl = None
+ now = time.time()
+ timeout = now + 1
+
+ if wrapper is not None:
+ timeout = now + 10
+
+ while not self.started:
+ self.farmer.loop(once=True)
+ if time.time() > timeout:
+ break
+
+ def pass_options(self, *opts):
+ if len(opts) > 0:
+ self.farmer.tell_monkey("OPTIONS " + (" ".join(['--' + opt for opt in 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]
+ elif what == 'EXIT':
+ if not self.stopped:
+ print("Unexpected exit of monkey process with code {}".format(args[0]))
+ assert self.stopped
+ else:
+ 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_LOGIN(self, action, _lwin, winid, *args):
+ if action == "OPEN":
+ new_win = LoginWindow(self, winid, *args)
+ self.logins[winid] = new_win
+ else:
+ win = self.logins.get(winid, None)
+ if win is None:
+ print(" Unknown login window id {}".format(winid))
+ else:
+ win.handle(action, *args)
+ if win.alive and win.ready:
+ self.handle_ready_login(win)
+
+ 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()]
+
+ def handle_ready_login(self, lwin):
+
+ # pylint: disable=locally-disabled, no-self-use
+
+ # Override this method to do useful stuff
+ lwin.destroy()
+
+
+class LoginWindow:
+
+ # pylint: disable=locally-disabled, too-many-instance-attributes, invalid-name
+
+ def __init__(self, browser, winid, _url, *url):
+ self.alive = True
+ self.ready = False
+ self.browser = browser
+ self.winid = winid
+ self.url = " ".join(url)
+ self.username = None
+ self.password = None
+ self.realm = None
+
+ def handle(self, action, _str="STR", *rest):
+ content = " ".join(rest)
+ if action == "USER":
+ self.username = content
+ elif action == "PASS":
+ self.password = content
+ elif action == "REALM":
+ self.realm = content
+ elif action == "DESTROY":
+ self.alive = False
+ else:
+ raise AssertionError("Unknown action {} for login window".format(action))
+ if not (self.username is None or self.password is None or self.realm is None):
+ self.ready = True
+
+ def send_username(self, username=None):
+ assert self.alive
+ if username is None:
+ username = self.username
+ self.browser.farmer.tell_monkey("LOGIN USERNAME {} {}".format(self.winid, username))
+
+ def send_password(self, password=None):
+ assert self.alive
+ if password is None:
+ password = self.password
+ self.browser.farmer.tell_monkey("LOGIN PASSWORD {} {}".format(self.winid, password))
+
+ def _wait_dead(self):
+ while self.alive:
+ self.browser.farmer.loop(once=True)
+
+ def go(self):
+ assert self.alive
+ self.browser.farmer.tell_monkey("LOGIN GO {}".format(self.winid))
+ self._wait_dead()
+
+ def destroy(self):
+ assert self.alive
+ self.browser.farmer.tell_monkey("LOGIN DESTROY {}".format(self.winid))
+ self._wait_dead()
+
+
+class BrowserWindow:
+
+ # pylint: disable=locally-disabled, too-many-instance-attributes, too-many-public-methods, invalid-name
+
+ def __init__(
+ self,
+ browser,
+ winid,
+ _for,
+ coreid,
+ _existing,
+ otherid,
+ _newtab,
+ newtab,
+ _clone,
+ clone):
+ # pylint: disable=locally-disabled, too-many-arguments
+ 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
+ self.log_entries = []
+ self.page_info_state = "UNKNOWN"
+
+ 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:
+ print("*** Timed out waiting for window to be destroyed")
+ print("*** URL was: {}".format(self.url))
+ print("*** Title was: {}".format(self.title))
+ print("*** Status was: {}".format(self.status))
+ 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 stop(self):
+ self.browser.farmer.tell_monkey("WINDOW STOP %s" % (self.winid))
+
+ def reload(self, all=False):
+ all = " ALL" if all else ""
+ self.browser.farmer.tell_monkey("WINDOW RELOAD %s%s" % (self.winid, all))
+ self.wait_start_loading()
+
+ def click(self, x, y, button="LEFT", kind="SINGLE"):
+ self.browser.farmer.tell_monkey("WINDOW CLICK WIN %s X %s Y %s BUTTON %s KIND %s" % (self.winid, x, y, button, kind))
+
+ def js_exec(self, src):
+ self.browser.farmer.tell_monkey("WINDOW EXEC WIN %s %s" % (self.winid, src))
+
+ 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_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):
+ # pylint: disable=locally-disabled, no-self-use
+
+ x = int(x)
+ y = int(y)
+ width = int(width)
+ height = int(height)
+
+ 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 handle_window_CONSOLE_LOG(self, _src, src, folding, level, *msg):
+ self.log_entries.append((src, folding == "FOLDABLE", level, " ".join(msg)))
+
+ def handle_window_PAGE_STATUS(self, _status, status):
+ self.page_info_state = status
+
+ 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
+
+ def clear_log(self):
+ self.log_entries = []
+
+ def log_contains(self, source=None, foldable=None, level=None, substr=None):
+ if (source is None) and (foldable is None) and (level is None) and (substr is None):
+ assert False, "Unable to run log_contains, no predicate given"
+
+ for (source_, foldable_, level_, msg_) in self.log_entries:
+ ok = True
+ if (source is not None) and (source != source_):
+ ok = False
+ if (foldable is not None) and (foldable != foldable_):
+ ok = False
+ if (level is not None) and (level != level_):
+ ok = False
+ if (substr is not None) and (substr not in msg_):
+ ok = False
+ if ok:
+ return True
+
+ return False
+
+ def wait_for_log(self, source=None, foldable=None, level=None, substr=None):
+ while not self.log_contains(source=source, foldable=foldable, level=level, substr=substr):
+ self.browser.farmer.loop(once=True)
+
+
+def farmer_test():
+ '''
+ Simple farmer test
+ '''
+
+ 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":
+ text_x = cmd[2]
+ text_y = cmd[4]
+ rest = " ".join(cmd[6:])
+ print("{} {} -> {}".format(text_x, text_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":
+ text_x = cmd[2]
+ text_y = cmd[4]
+ rest = " ".join(cmd[6:])
+ print("{} {} -> {}".format(text_x, text_y, rest))
+
+ browser.quit_and_wait()
+
+ class FooBarLogin(Browser):
+ def handle_ready_login(self, lwin):
+ lwin.send_username("foo")
+ lwin.send_password("bar")
+ lwin.go()
+
+ fbbrowser = FooBarLogin(quiet=True)
+ win = fbbrowser.new_window()
+ win.load_page("https://httpbin.org/basic-auth/foo/bar")
+ cmds = win.redraw()
+ print("Received {} plot commands for auth test".format(len(cmds)))
+ for cmd in cmds:
+ if cmd[0] == "TEXT":
+ text_x = cmd[2]
+ text_y = cmd[4]
+ rest = " ".join(cmd[6:])
+ print("{} {} -> {}".format(text_x, text_y, rest))
+
+ fname = "test/js/inserted-script.html"
+ full_fname = os.path.join(os.getcwd(), fname)
+
+ browser = Browser(quiet=True)
+ browser.pass_options("--enable_javascript=1")
+ win = browser.new_window()
+ win.load_page("file://" + full_fname)
+ print("Loaded, URL is {}".format(win.url))
+
+ win.wait_for_log(substr="deferred")
+
+ # print("Discussion was:")
+ # for line in browser.farmer.discussion:
+ # print("{} {}".format(line[0], line[1]))
+
+
+if __name__ == '__main__':
+ farmer_test()
diff --git a/test/nsoption.c b/test/nsoption.c
index 8f2388a5b..33da1f7e0 100644
--- a/test/nsoption.c
+++ b/test/nsoption.c
@@ -33,6 +33,10 @@
#include "utils/log.h"
#include "utils/nsoption.h"
+#ifndef TESTROOT
+#define TESTROOT "/tmp"
+#endif
+
const char *test_choices_path = "test/data/Choices";
const char *test_choices_short_path = "test/data/Choices-short";
const char *test_choices_all_path = "test/data/Choices-all";
@@ -49,7 +53,9 @@ static char *testnam(char *out)
{
static int count = 0;
static char name[64];
- snprintf(name, 64, "/tmp/nsoptiontest%d", count);
+ int pid;
+ pid=getpid();
+ snprintf(name, 64, TESTROOT"/nsoptiontest%d%d", pid, count);
count++;
return name;
}
@@ -241,7 +247,7 @@ struct format_test_vec_s format_test_vec[] = {
},
{
NSOPTION_sys_colour_ActiveBorder,
- "<tr><th>sys_colour_ActiveBorder</th><td>colour</td><td>default</td><td><span style=\"background-color: #d3d3d3; color: #000000; font-family:Monospace; \">#D3D3D3</span></td></tr>",
+ "<tr><th>sys_colour_ActiveBorder</th><td>colour</td><td>default</td><td><span style=\"font-family:Monospace;\">#D3D3D3</span> <span style=\"background-color: #d3d3d3; border: 1px solid #000000; display: inline-block; width: 1em; height: 1em;\"></span></td></tr>",
"sys_colour_ActiveBorder:d3d3d3"
},
};
diff --git a/test/nsurl.c b/test/nsurl.c
index ba024291b..631e7ae2c 100644
--- a/test/nsurl.c
+++ b/test/nsurl.c
@@ -428,9 +428,9 @@ static const struct test_pairs join_tests[] = {
{ " ", "http://a/b/c/d;p?q" },
{ "/", "http://a/" },
{ " / ", "http://a/" },
- { " ? ", "http://a/b/c/d;p?" },
+ { " ? ", "http://a/b/c/d;p" },
{ " h ", "http://a/b/c/h" },
- { "//foo?", "http://foo/?" },
+ { "//foo?", "http://foo/" },
{ "//foo#bar", "http://foo/#bar" },
{ "//foo/", "http://foo/" },
{ "http://<!--#echo var=", "http://<!--/#echo%20var="},
@@ -531,21 +531,25 @@ END_TEST
*/
static const struct test_triplets replace_query_tests[] = {
{ "http://netsurf-browser.org/?magical=true",
- "?magical=true&result=win",
+ "magical=true&result=win",
"http://netsurf-browser.org/?magical=true&result=win"},
{ "http://netsurf-browser.org/?magical=true#fragment",
- "?magical=true&result=win",
+ "magical=true&result=win",
"http://netsurf-browser.org/?magical=true&result=win#fragment"},
{ "http://netsurf-browser.org/#fragment",
- "?magical=true&result=win",
+ "magical=true&result=win",
"http://netsurf-browser.org/?magical=true&result=win#fragment"},
{ "http://netsurf-browser.org/path",
- "?magical=true",
+ "magical=true",
"http://netsurf-browser.org/path?magical=true"},
+ { "http://netsurf-browser.org/path?magical=true",
+ "",
+ "http://netsurf-browser.org/path"},
+
};
/**
@@ -655,7 +659,7 @@ static const struct test_compare component_tests[] = {
{ "http://u:p@a:66/b/c/d;p?q#f", "a", NSURL_HOST, true },
{ "http://u:p@a:66/b/c/d;p?q#f", "66", NSURL_PORT, true },
{ "http://u:p@a:66/b/c/d;p?q#f", "/b/c/d;p", NSURL_PATH, true },
- { "http://u:p@a:66/b/c/d;p?q#f", "?q", NSURL_QUERY, true },
+ { "http://u:p@a:66/b/c/d;p?q#f", "q", NSURL_QUERY, true },
{ "http://u:p@a:66/b/c/d;p?q#f", "f", NSURL_FRAGMENT, true },
{ "file:", "file", NSURL_SCHEME, true },
@@ -667,6 +671,11 @@ static const struct test_compare component_tests[] = {
{ "file:", NULL, NSURL_QUERY, false },
{ "file:", NULL, NSURL_FRAGMENT, false },
+ { "http://u:p@a:66/b/c/d;p?q=v#f", "q=v", NSURL_QUERY, true },
+ { "http://u:p@a:66/b/c/d;p?q=v", "q=v", NSURL_QUERY, true },
+ { "http://u:p@a:66/b/c/d;p?q=v&q1=v1#f", "q=v&q1=v1", NSURL_QUERY, true },
+ { "http://u:p@a:66/b/c/d;p?q=v&q1=v1", "q=v&q1=v1", NSURL_QUERY, true },
+
};
@@ -1167,12 +1176,11 @@ START_TEST(nsurl_api_assert_replace_query3_test)
nsurl *url;
nsurl *res;
nserror err;
- const char *rel = "moo";
err = nsurl_create(base_str, &url);
ck_assert(err == NSERROR_OK);
- err = nsurl_replace_query(url, rel, &res);
+ err = nsurl_replace_query(url, NULL, &res);
ck_assert(err != NSERROR_OK);
nsurl_unref(url);
diff --git a/test/utils.c b/test/utils.c
index 3d5319a28..9fe6747c3 100644
--- a/test/utils.c
+++ b/test/utils.c
@@ -37,22 +37,31 @@
#define SLEN(x) (sizeof((x)) - 1)
struct test_pairs {
- const unsigned long test;
+ const unsigned long long int test;
const char* res;
};
static const struct test_pairs human_friendly_bytesize_test_vec[] = {
- { 0, "0.00Bytes" },
- { 1024, "1024.00Bytes" },
- { 1025, "1.00kBytes" },
- { 1048576, "1024.00kBytes" },
- { 1048577, "1.00MBytes" },
- { 1073741824, "1024.00MBytes" },
- { 1073741888, "1024.00MBytes" }, /* spot the rounding error */
- { 1073741889, "1.00GBytes" },
- { 2147483648, "2.00GBytes" },
- { 3221225472, "3.00GBytes" },
- { 4294967295, "4.00GBytes" },
+ { 0ULL, "0Bytes" },
+ { 0x2AULL, "42Bytes" },
+ { 0x400ULL, "1024Bytes" },
+ { 0x401ULL, "1.00KiBytes" },
+ { 0xA9AEULL, "42.42KiBytes" },
+ { 0x100000ULL, "1024.00KiBytes" },
+ { 0x100001ULL, "1.00MiBytes" },
+ { 0x2A6B852ULL, "42.42MiBytes" },
+ { 0x40000000ULL, "1024.00MiBytes" },
+ { 0x40000001ULL, "1.00GiBytes" },
+ { 0x80000000ULL, "2.00GiBytes" },
+ { 0xC0000000ULL, "3.00GiBytes" },
+ { 0x100000000ULL, "4.00GiBytes" },
+ { 0x10000000000ULL, "1024.00GiBytes" },
+ { 0x10000000001ULL, "1.00TiBytes" },
+ { 0x4000000000000ULL, "1024.00TiBytes" },
+ { 0x4000000000001ULL, "1.00PiBytes" },
+ { 0x1000000000000000ULL, "1024.00PiBytes" },
+ { 0x1000000000000100ULL, "1.00EiBytes" }, /* precision loss */
+ { 0xFFFFFFFFFFFFFFFFULL, "16.00EiBytes" },
};
/**