forked from SumRndmDde/evil-haxe
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Setup tests for source maps (HaxeFoundation#6914)
* setup tests for sourcemaps * cleanup * removed FailExample
- Loading branch information
1 parent
b77546d
commit e3568be
Showing
13 changed files
with
331 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
-cp src | ||
-D eval-stack | ||
-main Test | ||
--interp |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import haxe.Json; | ||
import validation.ValidationReport; | ||
import haxe.io.Path; | ||
import sys.io.Process; | ||
import validation.Target; | ||
|
||
using sys.FileSystem; | ||
using StringTools; | ||
using Test; | ||
using Lambda; | ||
|
||
class Test { | ||
static public function main() { | ||
installDependencies(); | ||
|
||
var success = true; | ||
for(target in [Js, Php]) { | ||
Sys.println('[Testing ${target.getName()}]'); | ||
collectCases().iter(module -> success = runCase(module, target) && success); | ||
} | ||
|
||
Sys.exit(success ? 0 : 1); | ||
} | ||
|
||
static function runCase(module:String, target:Target):Bool { | ||
var proc = new Process( | ||
'haxe', | ||
targetCompilerFlags(target).concat([ | ||
'-cp', 'src', | ||
'-lib', 'sourcemap', | ||
'-D', 'source-map', | ||
'-D', 'analyzer-optimize', | ||
'-dce', 'full', | ||
'-main', module | ||
]) | ||
); | ||
var stdErr = proc.stderr.readAll().toString(); | ||
var stdOut = proc.stdout.readAll().toString(); | ||
if(stdErr.trim().length > 0) { | ||
Sys.println(stdErr.trim()); | ||
} | ||
|
||
var report:ValidationReport = try { | ||
Json.parse(stdOut); | ||
} catch(e:Dynamic) { | ||
Sys.println('$module: Failed to parse compilation result:\n$stdOut'); | ||
return false; | ||
} | ||
|
||
Sys.println('$module: ${report.summary.assertions} tests, ${report.summary.failures} failures'); | ||
|
||
if(!report.success) { | ||
for(error in report.errors) { | ||
Sys.println('${error.pos.file}:${error.pos.line}:${error.pos.column}: Failed to find expected code: ${error.expected}'); | ||
} | ||
} | ||
|
||
return report.success; | ||
} | ||
|
||
static function targetCompilerFlags(target:Target):Array<String> { | ||
return switch(target) { | ||
case Js: ['-js', 'bin/test.js']; | ||
case Php: ['-php', 'bin/php']; | ||
} | ||
} | ||
|
||
static function collectCases():Array<String> { | ||
var result = []; | ||
for(fileName in 'src/cases'.readDirectory()) { | ||
var path = new Path(fileName); | ||
if(path.isTestCase()) { | ||
result.push('cases.${path.file}'); | ||
} | ||
} | ||
return result; | ||
} | ||
|
||
static function isTestCase(path:Path):Bool { | ||
var firstChar = path.file.charAt(0); | ||
return path.ext == 'hx' && firstChar == firstChar.toUpperCase(); | ||
} | ||
|
||
static function installDependencies() { | ||
Sys.command('haxelib', ['install', 'sourcemap']); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
#if macro | ||
import validation.Target; | ||
import validation.Lines; | ||
import validation.ValidationReport; | ||
import validation.ValidationError; | ||
import validation.Exception; | ||
import haxe.display.Position.Location; | ||
import haxe.macro.Context; | ||
import haxe.macro.Expr; | ||
import haxe.macro.Compiler; | ||
import haxe.io.Path; | ||
|
||
using sys.io.File; | ||
using haxe.macro.Tools; | ||
using StringTools; | ||
#end | ||
|
||
class Validator { | ||
macro static public function expect(hxExpr:Expr, output:Expr):Expr { | ||
var outMap = try { | ||
parseExpectedOutput(output); | ||
} catch(e:ExpectedOutputException) { | ||
Context.error(e.msg, e.pos); | ||
return macro {}; | ||
} | ||
|
||
expected.push({pos:hxExpr.pos, out:outMap}); | ||
|
||
if(!onAfterGenerateAdded) { | ||
onAfterGenerateAdded = true; | ||
Context.onAfterGenerate(verifyExpectations); | ||
} | ||
|
||
return hxExpr; | ||
} | ||
|
||
#if macro | ||
static var onAfterGenerateAdded = false; | ||
static var expected = new Array<{pos:Position, out:Map<Target,String>}>(); | ||
static var sourceMapsCache = new Map<String,SourceMap>(); | ||
|
||
static function verifyExpectations() { | ||
var target = try { | ||
getCurrentTarget(); | ||
} catch(e:InvalidTargetException) { | ||
Context.error(e.msg, e.pos); | ||
return; | ||
} | ||
|
||
var errors = []; | ||
|
||
for(expectation in expected) { | ||
var expectedCode = expectation.out.get(target); | ||
var location = expectation.pos.toLocation(); | ||
var hxPos = location.range.start; | ||
var generated = getGeneratedContent(target, location); | ||
var found = false; | ||
//look for the mappings generated from the `hxPos` | ||
generated.map.eachMapping(genPos -> { | ||
if(found) return; | ||
if(genPos.originalLine == hxPos.line && genPos.originalColumn + 1 == hxPos.character) { | ||
var code = generated.code[genPos.generatedLine].substr(genPos.generatedColumn); | ||
//compare with expected output ignoring leading whitespaces | ||
if(code.trim().startsWith(expectedCode)) { | ||
found = true; | ||
} | ||
} | ||
}); | ||
if(!found) { | ||
errors.push({ | ||
expected: expectedCode, | ||
pos: { | ||
file: location.file, | ||
line: hxPos.line, | ||
column: hxPos.character | ||
} | ||
}); | ||
} | ||
} | ||
|
||
Sys.println(haxe.Json.stringify(buildValidationReport(errors), ' ')); | ||
} | ||
|
||
static function buildValidationReport(errors:Array<ValidationError>):ValidationReport { | ||
return { | ||
success: errors.length == 0, | ||
summary: { | ||
assertions: expected.length, | ||
failures: errors.length | ||
}, | ||
errors: errors | ||
} | ||
} | ||
|
||
static function getCurrentTarget():Target { | ||
return if(Context.defined('js')) { | ||
Js; | ||
} else if(Context.defined('php')) { | ||
Php; | ||
} else { | ||
throw new InvalidTargetException('Current target is not supported', haxe.macro.PositionTools.here()); | ||
} | ||
} | ||
|
||
static function getGeneratedContent(target:Target, location:Location):{code:Lines, map:SourceMap} { | ||
var sourceFile; | ||
var mapFile; | ||
switch(target) { | ||
case Js: | ||
sourceFile = Compiler.getOutput(); | ||
mapFile = '${Compiler.getOutput()}.map'; | ||
case Php: | ||
var phpFilePath = new Path(location.file.substr(location.file.indexOf('cases'))); | ||
phpFilePath.ext = 'php'; | ||
sourceFile = Path.join([Compiler.getOutput(), 'lib', phpFilePath.toString()]); | ||
mapFile = '$sourceFile.map'; | ||
} | ||
|
||
return { | ||
code: sourceFile.getContent().split('\n'), | ||
map: getSourceMap(mapFile) | ||
} | ||
} | ||
|
||
static function getSourceMap(mapFile:String):SourceMap { | ||
var sourceMap = sourceMapsCache.get(mapFile); | ||
if(sourceMap == null) { | ||
sourceMap = new SourceMap(mapFile.getContent()); | ||
sourceMapsCache.set(mapFile, sourceMap); | ||
} | ||
return sourceMap; | ||
} | ||
|
||
static function parseExpectedOutput(output:Expr):Null<Map<Target,String>> { | ||
var result = new Map(); | ||
switch(output) { | ||
case macro $a{exprs}: | ||
var expectedTargets = Target.getConstructors(); | ||
for(expr in exprs) { | ||
switch(expr) { | ||
case macro $target => ${{expr:EConst(CString(outStr))}}: | ||
var target = switch(target) { | ||
case macro Js: Js; | ||
case macro Php: Php; | ||
case _: throw new ExpectedOutputException('Invalid target in expected output: ${target.toString()}', target.pos); | ||
} | ||
expectedTargets.remove(target.getName()); | ||
result.set(target, outStr); | ||
case _: | ||
throw new ExpectedOutputException('Invalid item in map declaration for expected output. Should be `Target => "generated_code"`', expr.pos); | ||
} | ||
} | ||
if(expectedTargets.length > 0) { | ||
throw new ExpectedOutputException('Missing expected code for targets: ${expectedTargets.join(', ')}.', output.pos); | ||
} | ||
case _: | ||
throw new ExpectedOutputException('The second argument for "expect" should be a map declaration.', output.pos); | ||
} | ||
return result; | ||
|
||
} | ||
#end | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package cases; | ||
|
||
class Trace { | ||
static public function main() { | ||
expect(trace(''), [Js => "console.log", Php => "(Log::$trace)"]); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
import Validator.expect; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package validation; | ||
|
||
import haxe.macro.Expr; | ||
|
||
class Exception { | ||
public var msg(default,null):String; | ||
public var pos(default,null):Null<Position>; | ||
|
||
public function new(msg:String, ?pos:Position) { | ||
this.msg = msg; | ||
this.pos = pos; | ||
} | ||
} | ||
|
||
class ExpectedOutputException extends Exception {} | ||
|
||
class InvalidTargetException extends Exception {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package validation; | ||
|
||
/** | ||
* Represents some text content split in lines | ||
*/ | ||
abstract Lines(Array<String>) from Array<String> { | ||
/** | ||
* Get content on the specified 1-based line number | ||
*/ | ||
@:arrayAccess inline function get(lineNumber:Int):String { | ||
return this[lineNumber - 1]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package validation; | ||
|
||
enum Target { | ||
Js; | ||
Php; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package validation; | ||
|
||
typedef ValidationError = { | ||
/** Code expected to be generated from the haxe code */ | ||
var expected:String; | ||
/** Position of the haxe code, which is expected to be generated as `expected` code */ | ||
var pos:{ | ||
/** .hx File */ | ||
var file:String; | ||
/** 1-base line number in .hx file */ | ||
var line:Int; | ||
/** 1-base column number in .hx file */ | ||
var column:Int; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package validation; | ||
|
||
typedef ValidationReport = { | ||
var success:Bool; | ||
var summary:{ | ||
var assertions:Int; | ||
var failures:Int; | ||
}; | ||
var errors:Array<ValidationError>; | ||
} |