More robust system for testing compilation errors

This commit is contained in:
2024-02-14 21:14:01 -07:00
parent 4cdc874a94
commit 8f54d3992a
6 changed files with 158 additions and 22 deletions

10
src/kiss/EType.hx Normal file
View File

@@ -0,0 +1,10 @@
package kiss;
enum EType {
EStream(message:String);
EKiss(message:String);
EUnmatchedBracket(type:String);
EException(message:String);
EExpected(e:EType);
EUnexpected(e:Dynamic);
}

View File

@@ -17,7 +17,9 @@ import kiss.Macros;
import kiss.KissError;
import kiss.cloner.Cloner;
import tink.syntaxhub.*;
import tink.macro.Exprs;
import haxe.ds.Either;
import kiss.EType;
using kiss.Kiss;
using kiss.Helpers;
@@ -218,29 +220,73 @@ class Kiss {
return k;
}
public static function _try<T>(operation:() -> T):Null<T> {
public static function _try<T>(operation:() -> T, ?expectedError:EType):Null<T> {
#if !macrotest
try {
#end
return operation();
#if !macrotest
} catch (err:StreamError) {
Sys.stderr().writeString(err + "\n");
Sys.exit(1);
return null;
function printErr() {
Sys.stderr().writeString(err + "\n");
}
switch (expectedError) {
case EStream(message) if (message == err.message):
throw EExpected(expectedError);
case null:
printErr();
Sys.exit(1);
return null;
default:
printErr();
throw EUnexpected(err);
}
} catch (err:KissError) {
Sys.stderr().writeString(err + "\n");
Sys.exit(1);
return null;
function printErr() {
Sys.stderr().writeString(err + "\n");
}
switch (expectedError) {
case EKiss(message) if (message == err.message):
throw EExpected(expectedError);
case null:
printErr();
Sys.exit(1);
return null;
default:
printErr();
throw EUnexpected(err);
}
} catch (err:UnmatchedBracketSignal) {
Sys.stderr().writeString(Stream.toPrint(err.position) + ': Unmatched ${err.type}\n');
Sys.exit(1);
return null;
function printErr() {
Sys.stderr().writeString(Stream.toPrint(err.position) + ': Unmatched ${err.type}\n');
}
switch (expectedError) {
case EUnmatchedBracket(type) if (type == err.type):
throw EExpected(expectedError);
case null:
printErr();
Sys.exit(1);
return null;
default:
printErr();
throw EUnexpected(err);
}
} catch (err:Exception) {
Sys.stderr().writeString("Error: " + err.message + "\n");
Sys.stderr().writeString(err.stack.toString() + "\n");
Sys.exit(1);
return null;
function printErr() {
Sys.stderr().writeString("Error: " + err.message + "\n");
Sys.stderr().writeString(err.stack.toString() + "\n");
}
switch (expectedError) {
case EException(message) if (message == err.message):
throw EExpected(expectedError);
case null:
printErr();
Sys.exit(1);
return null;
default:
printErr();
throw EUnexpected(err);
}
}
#end
}
@@ -297,10 +343,77 @@ class Kiss {
}
}
// This is only for testing:
public static function buildExpectingError(expectedError:ExprOf<EType>, ?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array<Field> {
var buildFields = Context.getBuildFields();
var hasTestExpectedError = false;
for (field in buildFields) {
switch (field) {
case {
name: "testExpectedError",
kind: FFun({
params: [],
expr: {
expr: EBlock([{expr: ECall({expr: EConst(CIdent("_testExpectedError"))}, [])}])
}
})
}:
hasTestExpectedError = true;
default:
}
}
if (!hasTestExpectedError) {
throw "When building with Kiss.buildExpectingError(), you must add this Haxe function: " +
"function testExpectedError() { _testExpectedError(); }";
}
var expectedError = Exprs.eval(expectedError);
var s = Std.string(expectedError);
try {
build(kissFile, k, useClassFields, context, expectedError);
// Build success, which is bad:
buildFields.push({
pos: Context.currentPos(),
name: "_testExpectedError",
kind: FFun({
args: [],
expr: macro utest.Assert.fail('Build succeeded when an error was expected: ' + $v{s})
})
});
} catch (e:EType) {
switch (e) {
case EExpected(e):
buildFields.push({
pos: Context.currentPos(),
name: "_testExpectedError",
kind: FFun({
args: [],
expr: macro utest.Assert.pass()
})
});
case EUnexpected(e):
buildFields.push({
pos: Context.currentPos(),
name: "_testExpectedError",
kind: FFun({
args: [],
expr: macro utest.Assert.fail('Build failed in an unexpected way. Expected: ' + $v{s})
})
});
default:
throw "unexpected error is neither expected nor unexpected ¯\\_()_/¯";
}
}
return buildFields;
}
/**
Build macro: add fields to a class from a corresponding .kiss file
**/
public static function build(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext):Array<Field> {
public static function build(?kissFile:String, ?k:KissState, useClassFields = true, ?context:FrontendContext, ?expectedError:EType):Array<Field> {
var classPath = Context.getPosInfos(Context.currentPos()).file;
// (load... ) relative to the original file
@@ -364,7 +477,7 @@ class Kiss {
#end
k.fieldList;
});
}, expectedError);
return result;
}

View File

@@ -15,7 +15,7 @@ enum ErrorType {
// Internal Kiss errors
class KissError {
var exps:List<ReaderExp>;
var message:String;
public var message(default, null):String;
public function new(exps:Array<ReaderExp>, message:String) {
this.exps = exps;

View File

@@ -18,7 +18,7 @@ typedef Position = {
class StreamError {
var position:Position;
var message:String;
public var message(default,null):String;
public function new(position:Position, message:String) {
this.position = position;
@@ -54,7 +54,7 @@ class Stream {
var file = "string";
if (position != null) {
file = position.file;
}
}
var s = new Stream(file, content);
if (position != null) {
s.line = position.line;
@@ -140,7 +140,7 @@ class Stream {
startOfLine = false;
}
}
function record() {
recording += content.substr(0, count);
}
@@ -154,7 +154,7 @@ class Stream {
record();
default:
}
content = content.substr(count);
}
@@ -389,7 +389,7 @@ class Stream {
private var recordingType:StreamRecordType = Neither;
private var recording = "";
public function recordTransaction(type:StreamRecordType = Both, transaction:Void->Void) {
if (type == Neither) {
error(this, "Tried to start recording a transaction that would always return an empty string");

View File

@@ -0,0 +1,12 @@
package test.cases;
import utest.Test;
import utest.Assert;
import kiss.Prelude;
@:build(kiss.Kiss.buildExpectingError(kiss.EType.EUnmatchedBracket("}")))
class UnmatchedBracketTestCase extends Test {
function testExpectedError() {
_testExpectedError();
}
}

View File

@@ -0,0 +1 @@
}