summaryrefslogtreecommitdiffhomepage
path: root/tests/custom/02_runtime
diff options
context:
space:
mode:
authorPetr Štetiar <ynezz@true.cz>2021-03-19 16:54:55 +0100
committerJo-Philipp Wich <jo@mein.io>2021-04-23 00:42:30 +0200
commit2b59097c3f61fa901e91ac4cea48940760439578 (patch)
tree958d739a78f959dfcd55b3d76e6e970ca53fa1c6 /tests/custom/02_runtime
parent80393611fb6634abcc0da1dee2da7c4418dbde8d (diff)
tests: create custom tests from current tests cases
Signed-off-by: Petr Štetiar <ynezz@true.cz>
Diffstat (limited to 'tests/custom/02_runtime')
-rw-r--r--tests/custom/02_runtime/00_scoping161
-rw-r--r--tests/custom/02_runtime/01_break_continue50
-rw-r--r--tests/custom/02_runtime/02_this50
-rw-r--r--tests/custom/02_runtime/03_try_catch138
-rw-r--r--tests/custom/02_runtime/04_switch_case325
-rw-r--r--tests/custom/02_runtime/05_closure_scope35
-rw-r--r--tests/custom/02_runtime/06_recursion59
7 files changed, 818 insertions, 0 deletions
diff --git a/tests/custom/02_runtime/00_scoping b/tests/custom/02_runtime/00_scoping
new file mode 100644
index 0000000..5fadf43
--- /dev/null
+++ b/tests/custom/02_runtime/00_scoping
@@ -0,0 +1,161 @@
+Utpl implements function scoping, make sure that let variables are
+invisible outside of the function scope.
+
+-- Expect stdout --
+a_global=true
+a_local=true
+
+b_global=true
+b_local=false
+
+c_global=true
+c_local=false
+
+
+When seting a nonlocal variable, it is set in the nearest parent
+scope containing the variable or in the root scope if the variable
+was not found.
+
+x=2
+y=
+z=1
+
+
+Variables implicitly declared by for-in or counting for loops follow the same
+scoping rules.
+
+inner2 f_a=3
+inner2 f_b=
+inner2 f_c=3
+inner2 f_d=
+inner2 f_e=3
+
+inner f_a=3
+inner f_b=
+inner f_c=3
+inner f_d=
+inner f_e=3
+
+outer f_a=3
+outer f_b=
+outer f_c=
+outer f_d=
+outer f_e=3
+-- End --
+
+-- Testcase --
+{%
+ a_global = true;
+ let a_local = true;
+
+ function test() {
+ b_global = true;
+ let b_local = true;
+
+ function test2() {
+ c_global = true;
+ let c_local = true;
+ }
+
+ test2();
+ }
+
+ test();
+-%}
+
+a_global={{ !!a_global }}
+a_local={{ !!a_local }}
+
+b_global={{ !!b_global }}
+b_local={{ !!b_local }}
+
+c_global={{ !!c_global }}
+c_local={{ !!c_local }}
+
+
+When seting a nonlocal variable, it is set in the nearest parent
+scope containing the variable or in the root scope if the variable
+was not found.
+
+{%
+ x = 1;
+
+ function scope1() {
+ x = 2;
+ let y;
+
+ function scope2() {
+ // this does not set "y" in the root scope but overwrites the
+ // variable declared in the "scope1" function scope.
+ y = 2;
+
+ // this sets "z" in the root scope because it was not declared
+ // anywhere yet
+ z = 1;
+ }
+
+ scope2();
+ }
+
+ scope1();
+-%}
+
+x={{ x }}
+y={{ y }}
+z={{ z }}
+
+
+Variables implicitly declared by for-in or counting for loops follow the same
+scoping rules.
+
+{%
+ function scope3() {
+ // f_a is not declared local and be set in the root scope
+ for (f_a = 1; f_a < 3; f_a++)
+ ;
+
+ for (let f_b = 1; f_b < 3; f_b++)
+ ;
+
+ let f_c;
+
+ function scope4() {
+ // f_c is not declared local but declared in the parent scope, it
+ // will be set there
+ for (f_c in [1, 2, 3])
+ ;
+
+ for (let f_d in [1, 2, 3])
+ ;
+
+ // f_e is not declared, it will be set in the root scope
+ for (f_e in [1, 2, 3])
+ ;
+
+ print("inner2 f_a=", f_a, "\n");
+ print("inner2 f_b=", f_b, "\n");
+ print("inner2 f_c=", f_c, "\n");
+ print("inner2 f_d=", f_d, "\n");
+ print("inner2 f_e=", f_e, "\n");
+ print("\n");
+ }
+
+ scope4();
+
+ print("inner f_a=", f_a, "\n");
+ print("inner f_b=", f_b, "\n");
+ print("inner f_c=", f_c, "\n");
+ print("inner f_d=", f_d, "\n");
+ print("inner f_e=", f_e, "\n");
+ print("\n");
+ }
+
+ scope3();
+
+ print("outer f_a=", f_a, "\n");
+ print("outer f_b=", f_b, "\n");
+ print("outer f_c=", f_c, "\n");
+ print("outer f_d=", f_d, "\n");
+ print("outer f_e=", f_e, "\n");
+%}
+-- End --
diff --git a/tests/custom/02_runtime/01_break_continue b/tests/custom/02_runtime/01_break_continue
new file mode 100644
index 0000000..a27d072
--- /dev/null
+++ b/tests/custom/02_runtime/01_break_continue
@@ -0,0 +1,50 @@
+The "break" and "continue" statements allow to abort a running loop or to
+prematurely advance to the next cycle.
+
+-- Expect stdout --
+Testing break:
+ - Iteration 0
+ - Iteration 1
+ - Iteration 2
+ - Iteration 3
+ - Iteration 4
+ - Iteration 5
+ - Iteration 6
+ - Iteration 7
+ - Iteration 8
+ - Iteration 9
+ - Iteration 10
+
+Testing continue:
+ - Iteration 0
+ - Iteration 2
+ - Iteration 4
+ - Iteration 6
+ - Iteration 8
+-- End --
+
+-- Testcase --
+Testing break:
+{%
+ let i = 0;
+
+ while (true) {
+ print(" - Iteration ", i, "\n");
+
+ if (i == 10)
+ break;
+
+ i++;
+ }
+%}
+
+Testing continue:
+{%
+ for (i = 0; i < 10; i++) {
+ if (i % 2)
+ continue;
+
+ print(" - Iteration ", i, "\n");
+ }
+%}
+-- End --
diff --git a/tests/custom/02_runtime/02_this b/tests/custom/02_runtime/02_this
new file mode 100644
index 0000000..d8e85d2
--- /dev/null
+++ b/tests/custom/02_runtime/02_this
@@ -0,0 +1,50 @@
+The "this" object accesses the current function context.
+
+-- Expect stdout --
+true
+true
+-- End --
+
+-- Testcase --
+{%
+ // Functions not invoked on objects have no this context
+ function test() {
+ return (this === null);
+ }
+
+ // When invoked, "this" will point to the object containing the function
+ let o;
+ o = {
+ test: function() {
+ return (this === o);
+ }
+ };
+
+ print(test(), "\n");
+ print(o.test(), "\n");
+%}
+-- End --
+
+Test that the context is properly restored if function call arguments are
+dot or bracket expressions as well.
+
+-- Expect stdout --
+true
+true
+-- End --
+
+-- Testcase --
+{%
+ let o;
+ o = {
+ test: function() {
+ return (this === o);
+ }
+ };
+
+ let dummy = { foo: true, bar: false };
+
+ print(o.test(dummy.foo, dummy.bar), "\n");
+ print(o.test(dummy.foo, o.test(dummy.foo, dummy.bar)), "\n");
+%}
+-- End --
diff --git a/tests/custom/02_runtime/03_try_catch b/tests/custom/02_runtime/03_try_catch
new file mode 100644
index 0000000..751ca1d
--- /dev/null
+++ b/tests/custom/02_runtime/03_try_catch
@@ -0,0 +1,138 @@
+Wrapping an exeptional operation in try {} catch {} allows handling the
+resulting exception and to continue the execution flow.
+
+-- Expect stdout --
+Catched first exception.
+Catched second exception: exception 2.
+After exceptions.
+-- End --
+
+-- Testcase --
+{%
+ // A try-catch block that discards the exception information.
+ try {
+ die("exception 1");
+ }
+ catch {
+ print("Catched first exception.\n");
+ }
+
+ // A try-catch block that captures the resulting exception in
+ // the given variable.
+ try {
+ die("exception 2");
+ }
+ catch (e) {
+ print("Catched second exception: ", e, ".\n");
+ }
+
+ print("After exceptions.\n");
+%}
+-- End --
+
+
+Ensure that exceptions are propagated through C function calls.
+
+-- Expect stderr --
+exception
+In [anonymous function](), line 3, byte 18:
+ called from function replace ([C])
+ called from anonymous function ([stdin]:4:3)
+
+ ` die("exception");`
+ Near here -------------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ replace("test", "t", function(m) {
+ die("exception");
+ });
+%}
+-- End --
+
+
+Ensure that exception can be catched through C function calls.
+
+-- Expect stdout --
+Caught exception: exception
+-- End --
+
+-- Testcase --
+{%
+ try {
+ replace("test", "t", function(m) {
+ die("exception");
+ });
+ }
+ catch (e) {
+ print("Caught exception: ", e, "\n");
+ }
+%}
+-- End --
+
+
+Ensure that exceptions are propagated through user function calls.
+
+-- Expect stderr --
+exception
+In a(), line 3, byte 18:
+ called from function b ([stdin]:7:5)
+ called from function c ([stdin]:11:5)
+ called from anonymous function ([stdin]:14:4)
+
+ ` die("exception");`
+ Near here -------------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ function a() {
+ die("exception");
+ }
+
+ function b() {
+ a();
+ }
+
+ function c() {
+ b();
+ }
+
+ c();
+%}
+-- End --
+
+
+Ensure that exceptions can be caught in parent functions.
+
+-- Expect stdout --
+Caught exception: exception
+-- End --
+
+-- Testcase --
+{%
+ function a() {
+ die("exception");
+ }
+
+ function b() {
+ a();
+ }
+
+ function c() {
+ try {
+ b();
+ }
+ catch (e) {
+ print("Caught exception: ", e, "\n");
+ }
+ }
+
+ c();
+%}
+-- End --
diff --git a/tests/custom/02_runtime/04_switch_case b/tests/custom/02_runtime/04_switch_case
new file mode 100644
index 0000000..4c1fc57
--- /dev/null
+++ b/tests/custom/02_runtime/04_switch_case
@@ -0,0 +1,325 @@
+Testing utpl switch statements.
+
+
+1. Ensure that execution starts at the first matching case.
+
+-- Expect stdout --
+1a
+-- End --
+
+-- Testcase --
+{%
+ switch (1) {
+ case 1:
+ print("1a\n");
+ break;
+
+ case 1:
+ print("1b\n");
+ break;
+
+ case 2:
+ print("2\n");
+ break;
+ }
+%}
+-- End --
+
+
+2. Ensure that default case is only used if no case matches,
+ even if declared first.
+
+-- Expect stdout --
+1
+default
+-- End --
+
+-- Testcase --
+{%
+ for (n in [1, 3]) {
+ switch (n) {
+ default:
+ print("default\n");
+ break;
+
+ case 1:
+ print("1\n");
+ break;
+
+ case 2:
+ print("2\n");
+ break;
+ }
+ }
+%}
+-- End --
+
+
+3. Ensure that cases without break fall through into
+ subsequent cases.
+
+-- Expect stdout --
+1
+2
+default
+1
+2
+-- End --
+
+-- Testcase --
+{%
+ for (n in [1, 3]) {
+ switch (n) {
+ default:
+ print("default\n");
+
+ case 1:
+ print("1\n");
+
+ case 2:
+ print("2\n");
+ }
+ }
+%}
+-- End --
+
+
+4. Ensure that a single default case matches.
+
+-- Expect stdout --
+default
+default
+-- End --
+
+-- Testcase --
+{%
+ for (n in [1, 3]) {
+ switch (n) {
+ default:
+ print("default\n");
+ }
+ }
+%}
+-- End --
+
+
+5. Ensure that duplicate default cases emit a syntax
+ error during parsing.
+
+-- Expect stderr --
+Syntax error: more than one switch default case
+In line 6, byte 3:
+
+ ` default:`
+ ^-- Near here
+
+
+Syntax error: Expecting expression
+In line 8, byte 2:
+
+ ` }`
+ ^-- Near here
+
+
+-- End --
+
+-- Testcase --
+{%
+ switch (1) {
+ default:
+ print("default1\n");
+
+ default:
+ print("default2\n");
+ }
+%}
+-- End --
+
+
+6. Ensure that case values use strict comparison.
+
+-- Expect stdout --
+b
+b
+-- End --
+
+-- Testcase --
+{%
+ switch (1.0) {
+ case 1:
+ print("a\n");
+ break;
+
+ case 1.0:
+ print("b\n");
+ break;
+ }
+
+ switch ("123") {
+ case 123:
+ print("a\n");
+ break;
+
+ case "123":
+ print("b\n");
+ break;
+ }
+%}
+-- End --
+
+
+7. Ensure that case values may be complex expressions.
+
+-- Expect stdout --
+2, 3, 1
+-- End --
+
+-- Testcase --
+{%
+ switch (1) {
+ case a = 2, b = 3, c = 1:
+ print(join(", ", [ a, b, c ]), "\n");
+ break;
+ }
+%}
+-- End --
+
+
+8. Ensure that empty switch statements are accepted by the
+ parser and that the test expression is evaluated.
+
+-- Expect stdout --
+true
+-- End --
+
+-- Testcase --
+{%
+ x = false;
+
+ switch (x = true) {
+
+ }
+
+ print(x, "\n");
+%}
+-- End --
+
+
+9. Ensure that `return` breaks out of switch statements.
+
+-- Expect stdout --
+one
+two
+-- End --
+
+-- Testcase --
+{%
+ function test(n) {
+ switch (n) {
+ case 1:
+ return "one";
+
+ case 2:
+ return "two";
+
+ default:
+ return "three";
+ }
+ }
+
+ print(test(1), "\n");
+ print(test(2), "\n");
+%}
+-- End --
+
+
+10. Ensure that `continue` breaks out of switch statements.
+
+-- Expect stdout --
+one
+two
+-- End --
+
+-- Testcase --
+{%
+ for (n in [1,2]) {
+ switch (n) {
+ case 1:
+ print("one\n");
+ continue;
+
+ case 2:
+ print("two\n");
+ continue;
+
+ default:
+ print("three\n");
+ }
+ }
+%}
+-- End --
+
+
+11. Ensure that exceptions break out of switch statements.
+
+-- Expect stdout --
+one
+-- End --
+
+-- Expect stderr --
+Died
+In test(), line 6, byte 8:
+ called from anonymous function ([stdin]:17:14)
+
+ ` die();`
+ Near here ------^
+
+
+-- End --
+
+-- Testcase --
+{%
+ function test(n) {
+ switch (n) {
+ case 1:
+ print("one\n");
+ die();
+
+ case 2:
+ print("two\n");
+ die();
+
+ default:
+ print("three\n");
+ }
+ }
+
+ print(test(1), "\n");
+%}
+-- End --
+
+
+12. Ensure that consecutive cases values are properly handled.
+
+-- Expect stdout --
+three and four
+-- End --
+
+-- Testcase --
+{%
+ switch (3) {
+ case 1:
+ case 2:
+ print("one and two\n");
+ break;
+
+ case 3:
+ case 4:
+ print("three and four\n");
+ break;
+
+ default:
+ print("five\n");
+ }
+%}
+-- End --
diff --git a/tests/custom/02_runtime/05_closure_scope b/tests/custom/02_runtime/05_closure_scope
new file mode 100644
index 0000000..c59a433
--- /dev/null
+++ b/tests/custom/02_runtime/05_closure_scope
@@ -0,0 +1,35 @@
+Testing closure scopes.
+
+
+1. Ensure that the declaring scope is retained in functions.
+
+-- Expect stdout --
+Make function with x=1
+Make function with x=2
+Make function with x=3
+x is 1
+x is 2
+x is 3
+-- End --
+
+-- Testcase --
+{%
+ let count=0;
+
+ function a() {
+ let x = ++count;
+ print("Make function with x=", x, "\n");
+ return function() {
+ print("x is ", x, "\n");
+ };
+ }
+
+ let fn1 = a();
+ let fn2 = a();
+ let fn3 = a();
+
+ fn1();
+ fn2();
+ fn3();
+%}
+-- End --
diff --git a/tests/custom/02_runtime/06_recursion b/tests/custom/02_runtime/06_recursion
new file mode 100644
index 0000000..470fc3a
--- /dev/null
+++ b/tests/custom/02_runtime/06_recursion
@@ -0,0 +1,59 @@
+Testing recursive invocations.
+
+
+1. Testing recursive invocation.
+
+-- Expect stdout --
+1
+2
+4
+8
+16
+32
+64
+128
+256
+512
+1024
+2048
+4096
+8192
+16384
+-- End --
+
+-- Testcase --
+{%
+ function test(x) {
+ print(x, "\n");
+
+ if (x < 10000)
+ test(x * 2);
+ }
+
+ test(1);
+%}
+-- End --
+
+
+2. Testing infinite recursion.
+
+-- Expect stderr --
+Runtime error: Too much recursion
+In test(), line 3, byte 8:
+ called from anonymous function ([stdin]:6:7)
+
+ ` test();`
+ Near here ---^
+
+
+-- End --
+
+-- Testcase --
+{%
+ function test() {
+ test();
+ }
+
+ test();
+%}
+-- End --