Skip to content

Commit

Permalink
Setup tests for source maps (HaxeFoundation#6914)
Browse files Browse the repository at this point in the history
* setup tests for sourcemaps

* cleanup

* removed FailExample
  • Loading branch information
RealyUniqueName authored and Simn committed Apr 16, 2018
1 parent b77546d commit e3568be
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 1 deletion.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,7 @@ tests/server/test.js
tests/unit/pypy3-*
tmp.tmp

dev-display.hxml
dev-display.hxml

.DS_Store
tests/sourcemaps/bin
1 change: 1 addition & 0 deletions tests/runci/Config.hx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class Config {
static public final miscDir = cwd + "misc/";
static public final displayDir = cwd + "display/";
static public final serverDir = cwd + "server/";
static public final sourcemapsDir = cwd + "sourcemaps/";

static public final ci:Null<Ci> =
if (Sys.getEnv("TRAVIS") == "true")
Expand Down
3 changes: 3 additions & 0 deletions tests/runci/targets/Macro.hx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ class Macro {
changeDirectory(displayDir);
runCommand("haxe", ["build.hxml"]);

changeDirectory(sourcemapsDir);
runCommand("haxe", ["run.hxml"]);

changeDirectory(miscDir);
getCsDependencies();
getPythonDependencies();
Expand Down
4 changes: 4 additions & 0 deletions tests/sourcemaps/run.hxml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-cp src
-D eval-stack
-main Test
--interp
87 changes: 87 additions & 0 deletions tests/sourcemaps/src/Test.hx
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']);
}
}
163 changes: 163 additions & 0 deletions tests/sourcemaps/src/Validator.hx
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
}
7 changes: 7 additions & 0 deletions tests/sourcemaps/src/cases/Trace.hx
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)"]);
}
}
1 change: 1 addition & 0 deletions tests/sourcemaps/src/cases/import.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import Validator.expect;
17 changes: 17 additions & 0 deletions tests/sourcemaps/src/validation/Exception.hx
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 {}
13 changes: 13 additions & 0 deletions tests/sourcemaps/src/validation/Lines.hx
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];
}
}
6 changes: 6 additions & 0 deletions tests/sourcemaps/src/validation/Target.hx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package validation;

enum Target {
Js;
Php;
}
15 changes: 15 additions & 0 deletions tests/sourcemaps/src/validation/ValidationError.hx
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;
}
}
10 changes: 10 additions & 0 deletions tests/sourcemaps/src/validation/ValidationReport.hx
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>;
}

0 comments on commit e3568be

Please sign in to comment.