diff --git a/exec/tests/crypt/cert.js b/exec/tests/crypt/cert.js
new file mode 100644
index 0000000000000000000000000000000000000000..6cec4087a28480a20ef129a16c9c146c468c28d1
--- /dev/null
+++ b/exec/tests/crypt/cert.js
@@ -0,0 +1,12 @@
+var f = new File('test.crt');
+f.open('r');
+f.readln(65535);
+var b64 = f.readln(65535);
+var raw = base64_decode(b64, 819);
+var c = new CryptCert(raw);
+for (var i in c) {
+	var val = c[i];
+	if (val === undefined)
+		continue;
+}
+var x = JSON.stringify(c);
diff --git a/exec/tests/crypt/cryptcon.js b/exec/tests/crypt/cryptcon.js
new file mode 100644
index 0000000000000000000000000000000000000000..5699ec758cbaf38dea089eb112c080a86a1a292b
--- /dev/null
+++ b/exec/tests/crypt/cryptcon.js
@@ -0,0 +1,3 @@
+var x = JSON.stringify(CryptContext.ALGO);
+var cc = new CryptContext(CryptContext.ALGO.SHA2);
+var y = JSON.stringify(cc);
diff --git a/exec/tests/file/popen.js b/exec/tests/file/popen.js
new file mode 100644
index 0000000000000000000000000000000000000000..63acd7a101d2e7b441983771ce22988fd7891c8d
--- /dev/null
+++ b/exec/tests/file/popen.js
@@ -0,0 +1,11 @@
+if (system.platform == 'Win32')
+	exit();
+
+var f = new File("/bin/ls");
+if (!f.popen("r"))
+	throw new Error('popen() failed! {f.error}');
+while(f.is_open) {
+	var l = f.readln();
+	if (!l)
+		f.close();
+}
diff --git a/exec/tests/global/alert.js b/exec/tests/global/alert.js
new file mode 100644
index 0000000000000000000000000000000000000000..ccaaba0709a29ae0ae22dcced62e0478f2b23e9e
--- /dev/null
+++ b/exec/tests/global/alert.js
@@ -0,0 +1 @@
+alert("test");
diff --git a/exec/tests/global/beep.js b/exec/tests/global/beep.js
new file mode 100644
index 0000000000000000000000000000000000000000..63105c17838af1bdfaf2fec70642da815b4ada30
--- /dev/null
+++ b/exec/tests/global/beep.js
@@ -0,0 +1,3 @@
+beep();
+beep(420);
+beep(420, 300);
diff --git a/exec/tests/global/ctrl.js b/exec/tests/global/ctrl.js
new file mode 100644
index 0000000000000000000000000000000000000000..20e7adf7af2fa549b558636ca5cd650ae3f7a55e
--- /dev/null
+++ b/exec/tests/global/ctrl.js
@@ -0,0 +1,4 @@
+if (ctrl('C') !== String.fromCharCode(3))
+	throw new Error("ctrl('C') !== String.fromCharCode(3)");
+if (ctrl(3) !== String.fromCharCode(3))
+	throw new Error("ctrl(3) !== String.fromCharCode(3)");
diff --git a/exec/tests/global/exit.js b/exec/tests/global/exit.js
new file mode 100644
index 0000000000000000000000000000000000000000..47bd8d8709b42e1af50090d894459c9dc9fc7b18
--- /dev/null
+++ b/exec/tests/global/exit.js
@@ -0,0 +1,8 @@
+var x = random(256);
+var scope = new function(){};
+var ret = js.exec("exit_subproc.sjs", scope, x);
+if (ret != x) {
+	print('ret: '+ret.toSource());
+	print('x: '+x.toSource());
+	throw new Error("ret is wrong");
+}
diff --git a/exec/tests/global/exit_subproc.sjs b/exec/tests/global/exit_subproc.sjs
new file mode 100644
index 0000000000000000000000000000000000000000..96576f6bb863d097f46ddf32d81bb6efa549a1e4
--- /dev/null
+++ b/exec/tests/global/exit_subproc.sjs
@@ -0,0 +1 @@
+exit(argv[0]);
diff --git a/exec/tests/global/file_getname.js b/exec/tests/global/file_getname.js
new file mode 100644
index 0000000000000000000000000000000000000000..69b5ae72880ac1f95d248241be8b9c92195ffb0a
--- /dev/null
+++ b/exec/tests/global/file_getname.js
@@ -0,0 +1,2 @@
+if (file_getname("/home/cyan/thefilename").indexOf('/') !== -1)
+	throw new Error('slashes in filename');
diff --git a/exec/tests/global/load.js b/exec/tests/global/load.js
new file mode 100644
index 0000000000000000000000000000000000000000..b71370a03416e30ca9f9f5c9bed5e802be85fd70
--- /dev/null
+++ b/exec/tests/global/load.js
@@ -0,0 +1,39 @@
+const arg1 = random(256);
+// First, test that let and var go into the current global
+load("load_define.sjs", arg1);
+if (load_var !== arg1)
+	throw new Error('Var failed '+load_var+' != '+arg1);
+//if (load_let !== arg1)
+//	throw new Error('Let failed '+load_let+' != '+arg1);
+
+// Next, test the return value
+if (load("load_return.sjs", arg1) !== arg1)
+	throw new Error('load() return value bad.');
+
+// Now test what happens when a scope is passed...
+// Disabled for 1.8.5
+//var scope = {};
+//load(scope, "load_define.sjs", arg1);
+//if (scope.load_var !== arg1)
+//	throw new Error('xVar failed '+scope.load_var+' != '+arg1);
+
+// NOTE: When a scope is passed, it's absolutely entered then left.
+// This requires let support, var won't cut it.
+//if (scope.load_let === arg1)
+//	throw new Error('Let still in scope '+scope.load_let.toSource()+' == '+arg1.toSource());
+//if (scope.get_let() !== arg1)
+//	throw new Error("Scope closures don't work!");
+
+const arg2 = random(256);
+const arg3 = random(256);
+
+// Finally, test background threads...
+var q = load(true, "load_background.sjs", arg1, arg2, arg3);
+if (q.read(-1) !== arg1)
+	throw new Error('First result from queue incorrect');
+if (q.read(-1) !== arg2)
+	throw new Error('Second result from queue incorrect');
+if (q.read(-1) !== arg3)
+	throw new Error('Exit result from queue incorrect');
+if (q.orphan)
+	throw new Error('q orphaned when background terminates');
diff --git a/exec/tests/global/load_background.sjs b/exec/tests/global/load_background.sjs
new file mode 100644
index 0000000000000000000000000000000000000000..9de4e41758527ee955a1dc3038525ec415c97cd9
--- /dev/null
+++ b/exec/tests/global/load_background.sjs
@@ -0,0 +1,3 @@
+parent_queue.write(argv[0]);
+parent_queue.write(argv[1]);
+exit(argv[2]);
diff --git a/exec/tests/global/load_define.sjs b/exec/tests/global/load_define.sjs
new file mode 100644
index 0000000000000000000000000000000000000000..d1d1aa697ba193cfd8c2bafe2dc646c8fbf743d5
--- /dev/null
+++ b/exec/tests/global/load_define.sjs
@@ -0,0 +1,6 @@
+var load_let = argv[0];
+var load_var = argv[0];
+
+function get_let() {
+	return load_let;
+}
diff --git a/exec/tests/global/load_return.sjs b/exec/tests/global/load_return.sjs
new file mode 100644
index 0000000000000000000000000000000000000000..9c42c95272758d236b98474e1d36d01c37f3c934
--- /dev/null
+++ b/exec/tests/global/load_return.sjs
@@ -0,0 +1 @@
+argv[0];
diff --git a/exec/tests/global/log.js b/exec/tests/global/log.js
new file mode 100644
index 0000000000000000000000000000000000000000..0099607d420fc8fb6ab40786ad6c6757b60313ca
--- /dev/null
+++ b/exec/tests/global/log.js
@@ -0,0 +1,3 @@
+log("Test");
+log("10");
+log(1, "test");
diff --git a/exec/tests/global/mswait.js b/exec/tests/global/mswait.js
new file mode 100644
index 0000000000000000000000000000000000000000..966bf466240479dccbf966c026fe05ac3b939e5c
--- /dev/null
+++ b/exec/tests/global/mswait.js
@@ -0,0 +1,5 @@
+var start = system.timer;
+var slept = mswait(1000);
+var end = system.timer;
+if (Math.abs(end - (start + slept / 1000)) > 0.002)
+	throw new Error("mswait() = "+slept+" diff of "+Math.abs(end - (start + (slept / 1000))));
diff --git a/exec/tests/global/printf.js b/exec/tests/global/printf.js
new file mode 100644
index 0000000000000000000000000000000000000000..508a88da3cec6bfdc43561e183f5d5ad5e047705
--- /dev/null
+++ b/exec/tests/global/printf.js
@@ -0,0 +1,2 @@
+printf("%s", "string");
+printf("%s:%d\n", "string",57);
diff --git a/exec/tests/global/random.js b/exec/tests/global/random.js
new file mode 100644
index 0000000000000000000000000000000000000000..8b318540736417b1c3d053df09a3808a31e20ee4
--- /dev/null
+++ b/exec/tests/global/random.js
@@ -0,0 +1,8 @@
+random();
+if (random() == random() == random() == random())
+	throw new Error("Four random() calls returned same result");
+for (var i = 0; i < 1000; i++) {
+	var rval = random(10);
+	if (rval < 0 || rval >= 10)
+		throw new Error("random() result out of range");
+}
diff --git a/exec/tests/global/sound.js b/exec/tests/global/sound.js
new file mode 100644
index 0000000000000000000000000000000000000000..4166ad44b7d0a3fba28930d8d7242c6c3c25aaa6
--- /dev/null
+++ b/exec/tests/global/sound.js
@@ -0,0 +1 @@
+sound("sound.wav");
diff --git a/exec/tests/global/sound.wav b/exec/tests/global/sound.wav
new file mode 100644
index 0000000000000000000000000000000000000000..11f4c697dd1fc55879183b72eb38b03248fccbec
Binary files /dev/null and b/exec/tests/global/sound.wav differ
diff --git a/exec/tests/global/time.js b/exec/tests/global/time.js
new file mode 100644
index 0000000000000000000000000000000000000000..f144334e0578886c208247c3af95c53ba99a7ca0
--- /dev/null
+++ b/exec/tests/global/time.js
@@ -0,0 +1,4 @@
+var d = Date()/1000;
+var t = time();
+if (Math.abs(t - d) > 1)
+	throw new Error("new Date() and time() differ by over a second");
diff --git a/exec/tests/global/write.js b/exec/tests/global/write.js
new file mode 100644
index 0000000000000000000000000000000000000000..2c89ed53d33d3142082eea8a9b9c8a8e4a2d971f
--- /dev/null
+++ b/exec/tests/global/write.js
@@ -0,0 +1,2 @@
+write("value");
+write("value", "value");
diff --git a/exec/tests/global/writeln.js b/exec/tests/global/writeln.js
new file mode 100644
index 0000000000000000000000000000000000000000..962b5a5c7c985d6a39f750085380f2a0c5981df5
--- /dev/null
+++ b/exec/tests/global/writeln.js
@@ -0,0 +1,2 @@
+writeln("value");
+writeln("value", "value");
diff --git a/exec/tests/global/yield.js b/exec/tests/global/yield.js
new file mode 100644
index 0000000000000000000000000000000000000000..8c7aed79e34da29816eb94beabb239d393da5476
--- /dev/null
+++ b/exec/tests/global/yield.js
@@ -0,0 +1,3 @@
+yield();
+yield(false);
+yield(true);
diff --git a/exec/tests/test.js b/exec/tests/test.js
new file mode 100644
index 0000000000000000000000000000000000000000..ffb5ed189fb0998a80a548b7d63ae55a87e9c8ca
--- /dev/null
+++ b/exec/tests/test.js
@@ -0,0 +1,107 @@
+/*
+ * Ignore all the chicken/egg issues here...
+ *
+ * backslash()
+ * directory()
+ * format()
+ * js.exec()
+ * writeln()
+ * write()
+ * chdir()
+ */
+
+var testroot;
+if (argc < 1) {
+	try {
+		throw barfitty.barf(barf);
+	}
+	catch(e) {
+		testroot = e.fileName;
+	}
+	testroot = testroot.replace(/[\/\\][^\/\\]*$/,'');
+	testroot = backslash(testroot);
+}
+else
+	testroot = backslash(argv[0]);
+
+var testdirs = {};
+
+function depth_first(root, parent)
+{
+	var entries;
+
+	parent[root] = {tests:[]};
+	entries = directory(root+'*');
+
+	entries.forEach(function(entry) {
+		var last_ch;
+
+		if (entry === './' || entry === '../')
+			return;
+		if (entry.length < 1)
+			return;
+
+		last_ch = entry[entry.length - 1];
+		if (last_ch !== '/' && last_ch !== '\\') {
+			if (entry.substr(-3) === '.js')
+				this.tests.push(entry);
+			return;
+		}
+
+		depth_first(entry, this);
+	}, parent[root]);
+}
+
+depth_first(testroot, testdirs);
+// Do not run tests in top-level
+testdirs[testroot].tests = [];
+
+var passed = 0;
+var failed = 0;
+
+function run_tests(location, obj)
+{
+	if (testroot.length > location.substr)
+		stdout.writeln("Running tests in "+location.substr(testroot.length));
+	else
+		stdout.writeln("Running tests in "+location);
+
+	if (obj.tests !== undefined) {
+		obj.tests.forEach(function(testscript) {
+			var failed = false;
+
+			try {
+				var dir = testscript;
+				stdout.write(format("%-70.70s ", testscript.substr(location.length)+'......................................................................'));
+				chdir(location);
+				var result = js.exec(testscript, location, new function(){});
+				chdir(js.exec_dir);
+				if (result instanceof Error) {
+					failed = true;
+					log("Caught: "+result);
+				}
+			}
+			catch(e) {
+				failed = true;
+				log("Caught: "+e);
+			}
+			if (failed) {
+				stdout.writeln("FAILED!");
+				failed++;
+			}
+			else {
+				stdout.writeln("passed");
+				passed++;
+			}
+		});
+	}
+	stdout.writeln('');
+	Object.keys(obj).forEach(function(subname) {
+		if (subname === 'tests')
+			return;
+		run_tests(subname, obj[subname]);
+	});
+	stdout.writeln('');
+}
+
+run_tests(testroot, testdirs[testroot]);