/* ----------------------------------------------------------------------------- Copyright 2020 Kevin P. Barry Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ----------------------------------------------------------------------------- */ // Author: Kevin P. Barry [ta0kira@gmail.com] testcase "scoped unconditional" { success } unittest test { scoped { Int x <- 1 } in { x <- 2 } } testcase "unconditional has scoping" { error require "x" } unittest test { scoped { } in { Int x <- 1 } x <- 2 } testcase "return inside scope" { compiles } concrete Value {} define Value { @value process () -> (optional Value) process () { scoped { return empty } in \ empty } } testcase "return from scoped" { compiles } concrete Value {} define Value { @value process () -> (optional Value) process () { scoped { } in return empty } } testcase "update clashes with scoped" { error require "x" } unittest test { scoped { Int x <- 2 } in while (false) { } update { Int x <- 1 } } testcase "assign inside scope" { compiles } concrete Value {} define Value { @value process () -> (optional Value) process () (value) { scoped { value <- empty } in \ empty } } testcase "assign from scoped" { compiles } concrete Value {} define Value { @value process () -> (optional Value) process () (value) { scoped { } in value <- empty } } testcase "simple cleanup" { success } unittest test { Int value <- 0 scoped { value <- 1 } cleanup { value <- 2 } in value <- 3 if (value != 2) { fail(value) } } testcase "name clash in cleanup" { error require "value.+already defined" } unittest test { scoped { Int value <- 1 } cleanup { Int value <- 2 } in \ empty } testcase "cleanup before return" { success } unittest test { Value value <- Value.create() Int value1 <- value.call() if (value1 != 2) { fail(value1) } Int value2 <- value.get() if (value2 != 3) { fail(value2) } } concrete Value { @type create () -> (Value) @value call () -> (Int) @value get () -> (Int) } define Value { @value Int value create () { return Value{ 0 } } call () { value <- 1 scoped { value <- 2 } cleanup { value <- 3 } in return value } get () { return value } } testcase "positional return sets named returns for cleanup" { crash require "message" exclude "failed" } unittest test { String value, _ <- Test.get() } concrete Test { // Using a second return ensures that assignment works properly when the // ReturnTuple is constructed during assignment. @type get () -> (String,Int) } define Test { get () (value,value2) { value <- "failed" cleanup { fail(value) } in { return "message", 1 } } } testcase "top-level assignment sets named return for cleanup" { crash require "message" exclude "failed" } unittest test { String value <- Test.get() } concrete Test { @type get () -> (String) } define Test { get () (value) { value <- "failed" cleanup { fail(value) } in value <- "message" } } testcase "cannot refer to cleanup variables" { error require "value.+not defined" } concrete Test {} define Test { @type get () -> (Int) get () (value) { scoped { } cleanup { Int value2 <- 1 } in return value2 } } testcase "cleanup skipped in scoped return" { success } unittest test { Int value <- Test.get() if (value != 1) { fail(value) } } concrete Test { @type get () -> (Int) } define Test { get () { Int value <- 0 scoped { value <- 1 return value } cleanup { value <- 2 } in return 3 } } testcase "multiple cleanup" { success } unittest test { Int value1 <- 0 Int value2 <- 0 Int value3 <- 0 scoped { } cleanup { value1 <- 1 value2 <- 1 } in scoped { } cleanup { value2 <- 2 value3 <- 2 } in \ empty if (value1 != 1) { fail(value1) } if (value2 != 1) { fail(value2) } if (value3 != 2) { fail(value3) } } testcase "multiple cleanup with return" { success } unittest test { Value value <- Value.create() Int value1, Int value2, Int value3 <- value.call() if (value1 != 1) { fail(value1) } if (value2 != 1) { fail(value2) } if (value3 != 1) { fail(value3) } value1, value2, value3 <- value.get() if (value1 != 2) { fail(value1) } if (value2 != 2) { fail(value2) } if (value3 != 3) { fail(value3) } } concrete Value { @type create () -> (Value) @value call () -> (Int,Int,Int) @value get () -> (Int,Int,Int) } define Value { @value Int value1 @value Int value2 @value Int value3 create () { return Value{ 0, 0, 0 } } call () { value1 <- 1 value2 <- 1 value3 <- 1 scoped { } cleanup { value1 <- 2 value2 <- 2 } in scoped { } cleanup { value2 <- 3 value3 <- 3 } in return value1, value2, value3 } get () { return value1, value2, value3 } } testcase "cleanup can refer to top-level in variables" { success } unittest test { scoped { } cleanup { value <- 1 } in Int value <- 2 \ Testing.checkEquals(value,1) } testcase "cleanup cannot refer to later variables" { error require "value.+not defined" } unittest test { scoped { } cleanup { value <- 1 } in {} Int value <- 2 } testcase "cleanup cannot refer to nested in variables" { error require "value.+not defined" } unittest test { scoped { } cleanup { value <- 1 } in { Int value <- 2 } } testcase "cleanup not merged" { error require "value.+not defined" } unittest test { scoped { } cleanup { Int value <- 0 } in scoped { } cleanup { value <- 1 } in \ empty } testcase "no name clash in nested scope with return" { success } unittest test { scoped { } cleanup { Int value <- 0 } in scoped { } cleanup { Int value <- 1 } in return _ } testcase "cleanup skipped for fail" { crash require "scoped" } unittest test { scoped { String message <- "scoped" } cleanup { message <- "cleanup" } in fail(message) } testcase "cleanup not applied to returns outside of scope" { success } unittest test { Int value <- 0 scoped { } cleanup { if (value != 0) { fail(value) } } in \ empty value <- 1 return _ } testcase "cleanup unconditional" { success } unittest test { scoped { Int x <- 1 } in { \ x } } testcase "scoped empty blocks" { success } unittest test { scoped { // empty } cleanup { // empty } in { // empty } } testcase "just cleanup" { success } unittest test { Value value <- Value.create() Int value1 <- value.postIncrement() if (value1 != 0) { fail(value1) } Int value2 <- value.get() if (value2 != 1) { fail(value2) } } concrete Value { @type create () -> (Value) @value postIncrement () -> (Int) @value get () -> (Int) } define Value { @value Int value create () { return Value{ 0 } } postIncrement () { cleanup { value <- value+1 } in return value } get () { return value } } testcase "separate trace context for cleanup" { crash require "Failed" require "cleanup block" require "Test\.run" exclude "Failed.+Test\.run" exclude "Test\.run.+Failed" } unittest test { \ Test.run() } define Test { run () { cleanup { fail("Failed") } in return _ } } concrete Test { @type run () -> () } testcase "partial cleanup with while and break" { success } unittest test { Int x <- 0 Int y <- 0 Int z <- 0 cleanup { x <- 1 } in { while (true) { cleanup { y <- 2 } in if (true) { cleanup { z <- 3 } in if (true) { break } } } \ Testing.checkEquals(x,0) \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } \ Testing.checkEquals(x,1) \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } testcase "partial cleanup with while and continue" { success } unittest test { Int called <- 0 Int x <- 0 Int y <- 0 Int z <- 0 cleanup { x <- 1 } in { while ((called <- called+1) < 3) { if (called > 1) { \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } cleanup { y <- 2 } in if (true) { cleanup { z <- 3 } in if (true) { continue } } } \ Testing.checkEquals(called,3) \ Testing.checkEquals(x,0) \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } \ Testing.checkEquals(x,1) \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } testcase "full cleanup with while and return" { success } unittest test { Value value <- Value.create() scoped { Int x, Int y, Int z <- value.call() } in { \ Testing.checkEquals(x,0) \ Testing.checkEquals(y,0) \ Testing.checkEquals(z,0) } scoped { Int x, Int y, Int z <- value.get() } in { \ Testing.checkEquals(x,1) \ Testing.checkEquals(y,2) \ Testing.checkEquals(z,3) } } concrete Value { @type create () -> (Value) @value call () -> (Int,Int,Int) @value get () -> (Int,Int,Int) } define Value { @value Int x @value Int y @value Int z create () { return Value{ 0, 0, 0 } } call () { cleanup { x <- 1 } in while (true) { cleanup { y <- 2 } in if (true) { cleanup { z <- 3 } in if (true) { return get() } } } fail("Failed") } get () { return x, y, z } } testcase "named return cannot be modified in cleanup" { error require compiler "assign.+value" } concrete Test {} define Test { @type get () -> (String) get () (value) { cleanup { value <- "error" } in return "message" } } testcase "named return check in cleanup skipped if unreachable" { compiles } concrete Test {} define Test { @type get () -> (String) get () (value) { cleanup { fail(value) } in fail("message") } } testcase "positional return not allowed in cleanup" { error require compiler "return.+cleanup" } concrete Test {} define Test { @type get () -> (Int) get () (value) { cleanup { return 0 } in \ empty } } testcase "default return not allowed in cleanup" { error require compiler "return.+cleanup" } unittest test { cleanup { return _ } in \ empty }