Fixes #12: Handle differently loop case 1, improve tests and test runner
This commit is contained in:
Родитель
b14ff75250
Коммит
8b9f58ff2c
10
gulpfile.js
10
gulpfile.js
|
@ -80,8 +80,10 @@ gulp.task('watch', ['bundle'], function () {
|
|||
|
||||
gulp.task('test', function () {
|
||||
var tests = require('./release/tests/tests');
|
||||
tests.runDescriptiveTests(path.join(__dirname, './test-cases/first-mate/tests.json'));
|
||||
tests.runDescriptiveTests(path.join(__dirname, './test-cases/suite1/tests.json'));
|
||||
|
||||
tests.runMatcherTests(path.join(__dirname, './test-cases/matcher/testData.json'));
|
||||
tests.runTests([
|
||||
path.join(__dirname, './test-cases/first-mate/tests.json'),
|
||||
path.join(__dirname, './test-cases/suite1/tests.json')
|
||||
], [
|
||||
path.join(__dirname, './test-cases/matcher/testData.json')
|
||||
]);
|
||||
});
|
||||
|
|
|
@ -1312,10 +1312,13 @@ function _tokenizeString(grammar, lineText, isFirstLine, linePos, stack, lineTok
|
|||
handleCaptures(grammar, lineText, isFirstLine, stack, lineTokens, poppedRule.endCaptures, captureIndices);
|
||||
lineTokens.produce(stack, captureIndices[0].end);
|
||||
// pop
|
||||
stack.pop();
|
||||
var popped = stack.pop();
|
||||
if (!hasAdvanced && stackElement.enterPos === linePos) {
|
||||
// Grammar pushed & popped a rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 1');
|
||||
console.error('[1] - Grammar is in an endless loop - Grammar pushed & popped a rule without advancing');
|
||||
// See https://github.com/Microsoft/vscode-textmate/issues/12
|
||||
// Let's assume this was a mistake by the grammar author and the intent was to continue in this state
|
||||
stack.push(popped);
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
return false;
|
||||
|
@ -1343,7 +1346,7 @@ function _tokenizeString(grammar, lineText, isFirstLine, linePos, stack, lineTok
|
|||
}
|
||||
if (!hasAdvanced && stackElement.ruleId === stack[stack.length - 1].ruleId) {
|
||||
// Grammar pushed the same rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 2');
|
||||
console.error('[2] - Grammar is in an endless loop - Grammar pushed the same rule without advancing');
|
||||
stack.pop();
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
|
@ -1361,7 +1364,7 @@ function _tokenizeString(grammar, lineText, isFirstLine, linePos, stack, lineTok
|
|||
}
|
||||
if (!hasAdvanced && stackElement.ruleId === stack[stack.length - 1].ruleId) {
|
||||
// Grammar pushed the same rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 2');
|
||||
console.error('[3] - Grammar is in an endless loop - Grammar pushed the same rule without advancing');
|
||||
stack.pop();
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
|
@ -1376,7 +1379,7 @@ function _tokenizeString(grammar, lineText, isFirstLine, linePos, stack, lineTok
|
|||
stack.pop();
|
||||
if (!hasAdvanced) {
|
||||
// Grammar is not advancing, nor is it pushing/popping
|
||||
console.error('Grammar is in an endless loop - case 3');
|
||||
console.error('[4] - Grammar is in an endless loop - Grammar is not advancing, nor is it pushing/popping');
|
||||
if (stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
|
|
@ -6,67 +6,136 @@ var fs = require('fs');
|
|||
var path = require('path');
|
||||
var main_1 = require('../main');
|
||||
require('colors');
|
||||
var errCnt = 0;
|
||||
function runDescriptiveTests(testLocation) {
|
||||
var tests = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
errCnt = 0;
|
||||
tests.forEach(function (test, index) {
|
||||
var desc = test.desc;
|
||||
var noAsserts = (test.feature === 'endless-loop');
|
||||
console.log(index + ' - RUNNING ' + desc);
|
||||
var locator = {
|
||||
getFilePath: function (scopeName) { return null; },
|
||||
getInjections: function (scopeName) {
|
||||
if (scopeName === test.grammarScopeName) {
|
||||
return test.grammarInjections;
|
||||
}
|
||||
return void 0;
|
||||
var TestResult;
|
||||
(function (TestResult) {
|
||||
TestResult[TestResult["Pending"] = 0] = "Pending";
|
||||
TestResult[TestResult["Success"] = 1] = "Success";
|
||||
TestResult[TestResult["Failed"] = 2] = "Failed";
|
||||
})(TestResult || (TestResult = {}));
|
||||
var Test = (function () {
|
||||
function Test(testName, runner) {
|
||||
this.testName = testName;
|
||||
this._runner = runner;
|
||||
this.result = TestResult.Pending;
|
||||
this.failReason = null;
|
||||
}
|
||||
Test.prototype.run = function () {
|
||||
var ctx = new TestContext(this);
|
||||
try {
|
||||
this.result = TestResult.Success;
|
||||
this._runner(ctx);
|
||||
}
|
||||
catch (err) {
|
||||
this.result = TestResult.Failed;
|
||||
this.failReason = err;
|
||||
}
|
||||
};
|
||||
Test.prototype.fail = function (message, actual, expected) {
|
||||
this.result = TestResult.Failed;
|
||||
var reason = [
|
||||
message
|
||||
];
|
||||
if (actual) {
|
||||
'ACTUAL: ' + reason.push(JSON.stringify(actual, null, '\t'));
|
||||
}
|
||||
if (expected) {
|
||||
'EXPECTED: ' + reason.push(JSON.stringify(expected, null, '\t'));
|
||||
}
|
||||
this.failReason = reason.join('\n');
|
||||
};
|
||||
return Test;
|
||||
}());
|
||||
var TestContext = (function () {
|
||||
function TestContext(test) {
|
||||
this._test = test;
|
||||
}
|
||||
TestContext.prototype.fail = function (message, actual, expected) {
|
||||
this._test.fail(message, actual, expected);
|
||||
};
|
||||
return TestContext;
|
||||
}());
|
||||
var TestManager = (function () {
|
||||
function TestManager() {
|
||||
this._tests = [];
|
||||
}
|
||||
TestManager.prototype.registerTest = function (testName, runner) {
|
||||
this._tests.push(new Test(testName, runner));
|
||||
};
|
||||
TestManager.prototype.runTests = function () {
|
||||
var len = this._tests.length;
|
||||
for (var i = 0; i < len; i++) {
|
||||
var test = this._tests[i];
|
||||
var progress = (i + 1) + '/' + len;
|
||||
console.log(progress.yellow, ': ' + test.testName);
|
||||
test.run();
|
||||
if (test.result === TestResult.Failed) {
|
||||
console.log('FAILED'.red);
|
||||
console.log(test.failReason);
|
||||
}
|
||||
};
|
||||
var registry = new main_1.Registry(locator);
|
||||
var grammar = null;
|
||||
test.grammars.forEach(function (grammarPath) {
|
||||
var tmpGrammar = registry.loadGrammarFromPathSync(path.join(path.dirname(testLocation), grammarPath));
|
||||
if (test.grammarPath === grammarPath) {
|
||||
grammar = tmpGrammar;
|
||||
}
|
||||
var passed = this._tests.filter(function (t) { return t.result === TestResult.Success; });
|
||||
if (passed.length === len) {
|
||||
console.log((passed.length + '/' + len + ' PASSED.').green);
|
||||
}
|
||||
else {
|
||||
console.log((passed.length + '/' + len + ' PASSED.').red);
|
||||
}
|
||||
};
|
||||
return TestManager;
|
||||
}());
|
||||
function runTests(tokenizationTestPaths, matcherTestPaths) {
|
||||
var manager = new TestManager();
|
||||
tokenizationTestPaths.forEach(function (path) {
|
||||
generateTokenizationTests(manager, path);
|
||||
});
|
||||
matcherTestPaths.forEach(function (path) {
|
||||
generateMatcherTests(manager, path);
|
||||
});
|
||||
manager.runTests();
|
||||
}
|
||||
exports.runTests = runTests;
|
||||
function generateTokenizationTests(manager, testLocation) {
|
||||
var tests = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
var suiteName = path.join(path.basename(path.dirname(testLocation)), path.basename(testLocation));
|
||||
tests.forEach(function (test, index) {
|
||||
manager.registerTest(suiteName + ' > ' + test.desc, function (ctx) {
|
||||
var locator = {
|
||||
getFilePath: function (scopeName) { return null; },
|
||||
getInjections: function (scopeName) {
|
||||
if (scopeName === test.grammarScopeName) {
|
||||
return test.grammarInjections;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
};
|
||||
var registry = new main_1.Registry(locator);
|
||||
var grammar = null;
|
||||
test.grammars.forEach(function (grammarPath) {
|
||||
var tmpGrammar = registry.loadGrammarFromPathSync(path.join(path.dirname(testLocation), grammarPath));
|
||||
if (test.grammarPath === grammarPath) {
|
||||
grammar = tmpGrammar;
|
||||
}
|
||||
});
|
||||
if (test.grammarScopeName) {
|
||||
grammar = registry.grammarForScopeName(test.grammarScopeName);
|
||||
}
|
||||
var prevState = null;
|
||||
if (!grammar) {
|
||||
ctx.fail('I HAVE NO GRAMMAR FOR TEST');
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < test.lines.length; i++) {
|
||||
prevState = assertTokenization(ctx, grammar, test.lines[i], prevState);
|
||||
}
|
||||
});
|
||||
if (test.grammarScopeName) {
|
||||
grammar = registry.grammarForScopeName(test.grammarScopeName);
|
||||
}
|
||||
var prevState = null;
|
||||
if (!grammar) {
|
||||
console.error('I HAVE NO GRAMMAR FOR TEST ' + desc);
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < test.lines.length; i++) {
|
||||
prevState = assertTokenization(noAsserts, grammar, test.lines[i], prevState, desc);
|
||||
}
|
||||
});
|
||||
if (errCnt === 0) {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished ok';
|
||||
console.log(msg.green);
|
||||
}
|
||||
else {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished with ' + errCnt + ' errors.';
|
||||
console.log(msg.red);
|
||||
}
|
||||
}
|
||||
exports.runDescriptiveTests = runDescriptiveTests;
|
||||
function assertTokenization(noAsserts, grammar, testCase, prevState, desc) {
|
||||
function assertTokenization(ctx, grammar, testCase, prevState) {
|
||||
var r = grammar.tokenizeLine(testCase.line, prevState);
|
||||
if (!noAsserts) {
|
||||
assertTokens(r.tokens, testCase.line, testCase.tokens, desc);
|
||||
}
|
||||
assertTokens(ctx, r.tokens, testCase.line, testCase.tokens);
|
||||
return r.ruleStack;
|
||||
}
|
||||
function fail(message, actual, expected) {
|
||||
errCnt++;
|
||||
console.error(message.red);
|
||||
console.log(JSON.stringify(actual, null, '\t'));
|
||||
console.log(JSON.stringify(expected, null, '\t'));
|
||||
}
|
||||
function assertTokens(actual, line, expected, desc) {
|
||||
function assertTokens(ctx, actual, line, expected) {
|
||||
var actualTokens = actual.map(function (token) {
|
||||
return {
|
||||
value: line.substring(token.startIndex, token.endIndex),
|
||||
|
@ -80,32 +149,32 @@ function assertTokens(actual, line, expected, desc) {
|
|||
});
|
||||
}
|
||||
if (actualTokens.length !== expected.length) {
|
||||
fail('test: GOT DIFFERENT LENGTHS FOR ' + desc, actualTokens, expected);
|
||||
ctx.fail('GOT DIFFERENT LENGTHS FOR ', actualTokens, expected);
|
||||
return;
|
||||
}
|
||||
for (var i = 0, len = actualTokens.length; i < len; i++) {
|
||||
assertToken(actualTokens[i], expected[i], desc);
|
||||
assertToken(ctx, actualTokens[i], expected[i]);
|
||||
}
|
||||
}
|
||||
function assertToken(actual, expected, desc) {
|
||||
function assertToken(ctx, actual, expected) {
|
||||
if (actual.value !== expected.value) {
|
||||
fail('test: GOT DIFFERENT VALUES FOR ' + desc, actual.value, expected.value);
|
||||
ctx.fail('test: GOT DIFFERENT VALUES FOR ', actual.value, expected.value);
|
||||
return;
|
||||
}
|
||||
if (actual.scopes.length !== expected.scopes.length) {
|
||||
fail('test: GOT DIFFERENT scope lengths FOR ' + desc, actual.scopes, expected.scopes);
|
||||
ctx.fail('test: GOT DIFFERENT scope lengths FOR ', actual.scopes, expected.scopes);
|
||||
return;
|
||||
}
|
||||
for (var i = 0, len = actual.scopes.length; i < len; i++) {
|
||||
if (actual.scopes[i] !== expected.scopes[i]) {
|
||||
fail('test: GOT DIFFERENT scopes FOR ' + desc, actual.scopes, expected.scopes);
|
||||
ctx.fail('test: GOT DIFFERENT scopes FOR ', actual.scopes, expected.scopes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
function runMatcherTests(testLocation, testNum) {
|
||||
if (testNum === void 0) { testNum = -1; }
|
||||
function generateMatcherTests(manager, testLocation) {
|
||||
var tests = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
var suiteName = path.join(path.basename(path.dirname(testLocation)), path.basename(testLocation));
|
||||
var nameMatcher = function (identifers, stackElements) {
|
||||
var lastIndex = 0;
|
||||
return identifers.every(function (identifier) {
|
||||
|
@ -118,29 +187,13 @@ function runMatcherTests(testLocation, testNum) {
|
|||
return false;
|
||||
});
|
||||
};
|
||||
var errCnt = 0;
|
||||
tests.forEach(function (test, index) {
|
||||
if (testNum !== -1 && testNum !== index) {
|
||||
return;
|
||||
}
|
||||
var matcher = main_1.createMatcher(test.expression, nameMatcher);
|
||||
var result = matcher(test.input);
|
||||
if (result === test.result) {
|
||||
console.log(index + ': passed');
|
||||
}
|
||||
else {
|
||||
var message = index + ': failed , expected ' + test.result;
|
||||
console.error(message.red);
|
||||
errCnt++;
|
||||
}
|
||||
manager.registerTest(suiteName + ' > ' + index, function (ctx) {
|
||||
var matcher = main_1.createMatcher(test.expression, nameMatcher);
|
||||
var result = matcher(test.input);
|
||||
if (result !== test.result) {
|
||||
ctx.fail('matcher expected', result, test.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (errCnt === 0) {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished ok';
|
||||
console.log(msg.green);
|
||||
}
|
||||
else {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished with ' + errCnt + ' errors.';
|
||||
console.log(msg.red);
|
||||
}
|
||||
}
|
||||
exports.runMatcherTests = runMatcherTests;
|
||||
|
|
|
@ -486,13 +486,19 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
|
|||
lineTokens.produce(stack, captureIndices[0].end);
|
||||
|
||||
// pop
|
||||
stack.pop();
|
||||
let popped = stack.pop();
|
||||
|
||||
if (!hasAdvanced && stackElement.enterPos === linePos) {
|
||||
// Grammar pushed & popped a rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 1');
|
||||
console.error('[1] - Grammar is in an endless loop - Grammar pushed & popped a rule without advancing');
|
||||
|
||||
// See https://github.com/Microsoft/vscode-textmate/issues/12
|
||||
// Let's assume this was a mistake by the grammar author and the intent was to continue in this state
|
||||
stack.push(popped);
|
||||
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
|
||||
return false;
|
||||
}
|
||||
} else if (matchedRuleId === -3) {
|
||||
|
@ -523,7 +529,7 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
|
|||
|
||||
if (!hasAdvanced && stackElement.ruleId === stack[stack.length - 1].ruleId) {
|
||||
// Grammar pushed the same rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 2');
|
||||
console.error('[2] - Grammar is in an endless loop - Grammar pushed the same rule without advancing');
|
||||
stack.pop();
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
|
@ -543,7 +549,7 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
|
|||
|
||||
if (!hasAdvanced && stackElement.ruleId === stack[stack.length - 1].ruleId) {
|
||||
// Grammar pushed the same rule without advancing
|
||||
console.error('Grammar is in an endless loop - case 2');
|
||||
console.error('[3] - Grammar is in an endless loop - Grammar pushed the same rule without advancing');
|
||||
stack.pop();
|
||||
lineTokens.produce(stack, lineLength);
|
||||
linePos = lineLength;
|
||||
|
@ -560,7 +566,7 @@ function _tokenizeString(grammar: Grammar, lineText: OnigString, isFirstLine: bo
|
|||
|
||||
if (!hasAdvanced) {
|
||||
// Grammar is not advancing, nor is it pushing/popping
|
||||
console.error('Grammar is in an endless loop - case 3');
|
||||
console.error('[4] - Grammar is in an endless loop - Grammar is not advancing, nor is it pushing/popping');
|
||||
if (stack.length > 1) {
|
||||
stack.pop();
|
||||
}
|
||||
|
|
|
@ -9,61 +9,157 @@ import {Registry, createMatcher, IGrammarLocator} from '../main';
|
|||
import {IToken, StackElement, IGrammar} from '../grammar';
|
||||
import 'colors';
|
||||
|
||||
var errCnt = 0;
|
||||
enum TestResult {
|
||||
Pending,
|
||||
Success,
|
||||
Failed
|
||||
}
|
||||
|
||||
export function runDescriptiveTests(testLocation: string) {
|
||||
let tests:IRawTest[] = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
class Test {
|
||||
testName:string;
|
||||
result:TestResult;
|
||||
failReason: string;
|
||||
|
||||
errCnt = 0;
|
||||
tests.forEach(function(test, index) {
|
||||
let desc = test.desc;
|
||||
let noAsserts = (test.feature === 'endless-loop');
|
||||
private _runner: (ctx:TestContext) => void;
|
||||
|
||||
console.log(index + ' - RUNNING ' + desc);
|
||||
let locator : IGrammarLocator = {
|
||||
getFilePath: (scopeName:string) => null,
|
||||
getInjections: (scopeName:string) => {
|
||||
if (scopeName === test.grammarScopeName) {
|
||||
return test.grammarInjections;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
let registry = new Registry(locator);
|
||||
let grammar: IGrammar = null;
|
||||
test.grammars.forEach(function(grammarPath) {
|
||||
let tmpGrammar = registry.loadGrammarFromPathSync(path.join(path.dirname(testLocation), grammarPath));
|
||||
if (test.grammarPath === grammarPath) {
|
||||
grammar = tmpGrammar;
|
||||
}
|
||||
});
|
||||
if (test.grammarScopeName) {
|
||||
grammar = registry.grammarForScopeName(test.grammarScopeName);
|
||||
}
|
||||
let prevState: StackElement[] = null;
|
||||
if (!grammar) {
|
||||
console.error('I HAVE NO GRAMMAR FOR TEST ' + desc);
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < test.lines.length; i++) {
|
||||
prevState = assertTokenization(noAsserts, grammar, test.lines[i], prevState, desc);
|
||||
}
|
||||
});
|
||||
|
||||
if (errCnt === 0) {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished ok';
|
||||
console.log((<any>msg).green);
|
||||
} else {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished with ' + errCnt + ' errors.';
|
||||
console.log((<any>msg).red);
|
||||
constructor(testName:string, runner: (ctx:TestContext) => void) {
|
||||
this.testName = testName;
|
||||
this._runner = runner;
|
||||
this.result = TestResult.Pending;
|
||||
this.failReason = null;
|
||||
}
|
||||
|
||||
public run(): void {
|
||||
let ctx = new TestContext(this);
|
||||
try {
|
||||
this.result = TestResult.Success;
|
||||
this._runner(ctx);
|
||||
} catch(err) {
|
||||
this.result = TestResult.Failed;
|
||||
this.failReason = err;
|
||||
}
|
||||
}
|
||||
|
||||
public fail<T>(message:string, actual:T, expected:T): void {
|
||||
this.result = TestResult.Failed;
|
||||
let reason = [
|
||||
message
|
||||
];
|
||||
if (actual) {
|
||||
'ACTUAL: ' + reason.push(JSON.stringify(actual, null, '\t'))
|
||||
}
|
||||
if (expected) {
|
||||
'EXPECTED: ' + reason.push(JSON.stringify(expected, null, '\t'))
|
||||
}
|
||||
this.failReason = reason.join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
class TestContext {
|
||||
|
||||
private _test: Test;
|
||||
|
||||
constructor(test: Test) {
|
||||
this._test = test;
|
||||
}
|
||||
|
||||
public fail<T>(message:string, actual?:T, expected?:T): void {
|
||||
this._test.fail(message, actual, expected);
|
||||
}
|
||||
}
|
||||
|
||||
class TestManager {
|
||||
|
||||
private _tests:Test[];
|
||||
|
||||
constructor() {
|
||||
this._tests = [];
|
||||
}
|
||||
|
||||
registerTest(testName: string, runner:(ctx:TestContext)=>void): void {
|
||||
this._tests.push(new Test(testName, runner));
|
||||
}
|
||||
|
||||
runTests(): void {
|
||||
|
||||
let len = this._tests.length;
|
||||
|
||||
for (let i = 0; i < len; i++) {
|
||||
let test = this._tests[i];
|
||||
let progress = (i + 1) + '/' + len;
|
||||
console.log((<any>progress).yellow, ': ' + test.testName);
|
||||
test.run();
|
||||
if (test.result === TestResult.Failed) {
|
||||
console.log((<any>'FAILED').red);
|
||||
console.log(test.failReason);
|
||||
}
|
||||
}
|
||||
|
||||
let passed = this._tests.filter(t => t.result === TestResult.Success);
|
||||
|
||||
if (passed.length === len) {
|
||||
console.log((<any>(passed.length + '/' + len + ' PASSED.')).green);
|
||||
} else {
|
||||
console.log((<any>(passed.length + '/' + len + ' PASSED.')).red);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function runTests(tokenizationTestPaths:string[], matcherTestPaths:string[]): void {
|
||||
let manager = new TestManager();
|
||||
|
||||
tokenizationTestPaths.forEach((path) => {
|
||||
generateTokenizationTests(manager, path)
|
||||
});
|
||||
|
||||
matcherTestPaths.forEach((path) => {
|
||||
generateMatcherTests(manager, path);
|
||||
});
|
||||
|
||||
manager.runTests();
|
||||
}
|
||||
|
||||
function generateTokenizationTests(manager:TestManager, testLocation: string): void {
|
||||
let tests:IRawTest[] = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
|
||||
let suiteName = path.join(path.basename(path.dirname(testLocation)), path.basename(testLocation));
|
||||
tests.forEach(function(test, index) {
|
||||
manager.registerTest(suiteName + ' > ' + test.desc, (ctx) => {
|
||||
let locator : IGrammarLocator = {
|
||||
getFilePath: (scopeName:string) => null,
|
||||
getInjections: (scopeName:string) => {
|
||||
if (scopeName === test.grammarScopeName) {
|
||||
return test.grammarInjections;
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
}
|
||||
|
||||
let registry = new Registry(locator);
|
||||
let grammar: IGrammar = null;
|
||||
test.grammars.forEach(function(grammarPath) {
|
||||
let tmpGrammar = registry.loadGrammarFromPathSync(path.join(path.dirname(testLocation), grammarPath));
|
||||
if (test.grammarPath === grammarPath) {
|
||||
grammar = tmpGrammar;
|
||||
}
|
||||
});
|
||||
if (test.grammarScopeName) {
|
||||
grammar = registry.grammarForScopeName(test.grammarScopeName);
|
||||
}
|
||||
let prevState: StackElement[] = null;
|
||||
if (!grammar) {
|
||||
ctx.fail('I HAVE NO GRAMMAR FOR TEST');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < test.lines.length; i++) {
|
||||
prevState = assertTokenization(ctx, grammar, test.lines[i], prevState);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface IRawTest {
|
||||
desc: string;
|
||||
feature: string;
|
||||
grammars: string[];
|
||||
grammarPath?: string;
|
||||
grammarScopeName?: string;
|
||||
|
@ -81,22 +177,13 @@ interface IRawToken {
|
|||
scopes: string[];
|
||||
}
|
||||
|
||||
function assertTokenization(noAsserts:boolean, grammar:IGrammar, testCase:IRawTestLine, prevState: StackElement[], desc:string): StackElement[] {
|
||||
function assertTokenization(ctx:TestContext, grammar:IGrammar, testCase:IRawTestLine, prevState: StackElement[]): StackElement[] {
|
||||
let r = grammar.tokenizeLine(testCase.line, prevState);
|
||||
if (!noAsserts) {
|
||||
assertTokens(r.tokens, testCase.line, testCase.tokens, desc);
|
||||
}
|
||||
assertTokens(ctx, r.tokens, testCase.line, testCase.tokens);
|
||||
return r.ruleStack;
|
||||
}
|
||||
|
||||
function fail<T>(message:string, actual:T, expected:T): void {
|
||||
errCnt++;
|
||||
console.error((<any>message).red);
|
||||
console.log(JSON.stringify(actual, null, '\t'));
|
||||
console.log(JSON.stringify(expected, null, '\t'));
|
||||
}
|
||||
|
||||
function assertTokens(actual:IToken[], line:string, expected:IRawToken[], desc:string): void {
|
||||
function assertTokens(ctx:TestContext, actual:IToken[], line:string, expected:IRawToken[]): void {
|
||||
let actualTokens:IRawToken[] = actual.map(function(token) {
|
||||
return {
|
||||
value: line.substring(token.startIndex, token.endIndex),
|
||||
|
@ -110,26 +197,26 @@ function assertTokens(actual:IToken[], line:string, expected:IRawToken[], desc:s
|
|||
});
|
||||
}
|
||||
if (actualTokens.length !== expected.length) {
|
||||
fail('test: GOT DIFFERENT LENGTHS FOR ' + desc, actualTokens, expected);
|
||||
ctx.fail('GOT DIFFERENT LENGTHS FOR ', actualTokens, expected);
|
||||
return;
|
||||
}
|
||||
for (let i = 0, len = actualTokens.length; i < len; i++) {
|
||||
assertToken(actualTokens[i], expected[i], desc);
|
||||
assertToken(ctx, actualTokens[i], expected[i]);
|
||||
}
|
||||
}
|
||||
|
||||
function assertToken(actual:IRawToken, expected:IRawToken, desc:string): void {
|
||||
function assertToken(ctx:TestContext, actual:IRawToken, expected:IRawToken): void {
|
||||
if (actual.value !== expected.value) {
|
||||
fail('test: GOT DIFFERENT VALUES FOR ' + desc, actual.value, expected.value);
|
||||
ctx.fail('test: GOT DIFFERENT VALUES FOR ', actual.value, expected.value);
|
||||
return;
|
||||
}
|
||||
if (actual.scopes.length !== expected.scopes.length) {
|
||||
fail('test: GOT DIFFERENT scope lengths FOR ' + desc, actual.scopes, expected.scopes);
|
||||
ctx.fail('test: GOT DIFFERENT scope lengths FOR ', actual.scopes, expected.scopes);
|
||||
return;
|
||||
}
|
||||
for (let i = 0, len = actual.scopes.length; i < len; i++) {
|
||||
if (actual.scopes[i] !== expected.scopes[i]) {
|
||||
fail('test: GOT DIFFERENT scopes FOR ' + desc, actual.scopes, expected.scopes);
|
||||
ctx.fail('test: GOT DIFFERENT scopes FOR ', actual.scopes, expected.scopes);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -141,8 +228,9 @@ interface IMatcherTest {
|
|||
result: boolean;
|
||||
}
|
||||
|
||||
export function runMatcherTests(testLocation: string, testNum =-1) {
|
||||
function generateMatcherTests(manager:TestManager, testLocation: string) {
|
||||
let tests:IMatcherTest[] = JSON.parse(fs.readFileSync(testLocation).toString());
|
||||
let suiteName = path.join(path.basename(path.dirname(testLocation)), path.basename(testLocation));
|
||||
|
||||
var nameMatcher = (identifers: string[], stackElements: string[]) => {
|
||||
var lastIndex = 0;
|
||||
|
@ -156,27 +244,13 @@ export function runMatcherTests(testLocation: string, testNum =-1) {
|
|||
return false;
|
||||
});
|
||||
};
|
||||
var errCnt = 0;
|
||||
tests.forEach((test, index) => {
|
||||
if (testNum !== -1 && testNum !== index) {
|
||||
return;
|
||||
}
|
||||
|
||||
var matcher = createMatcher(test.expression, nameMatcher);
|
||||
var result = matcher(test.input);
|
||||
if (result === test.result) {
|
||||
console.log(index + ': passed');
|
||||
} else {
|
||||
var message = index + ': failed , expected ' + test.result;
|
||||
console.error((<any>message).red);
|
||||
errCnt++;
|
||||
}
|
||||
manager.registerTest(suiteName + ' > ' + index, (ctx) => {
|
||||
var matcher = createMatcher(test.expression, nameMatcher);
|
||||
var result = matcher(test.input);
|
||||
if (result !== test.result) {
|
||||
ctx.fail('matcher expected', result, test.result);
|
||||
}
|
||||
});
|
||||
});
|
||||
if (errCnt === 0) {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished ok';
|
||||
console.log((<any>msg).green);
|
||||
} else {
|
||||
var msg = 'Test suite at ' + testLocation + ' finished with ' + errCnt + ' errors.';
|
||||
console.log((<any>msg).red);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2680,7 +2680,21 @@
|
|||
"grammarPath": "fixtures/infinite-loop.json",
|
||||
"lines": [
|
||||
{
|
||||
"line": "abc"
|
||||
"line": "abc",
|
||||
"tokens": [{
|
||||
"value": "a",
|
||||
"scopes": [
|
||||
"source.infinite-loop",
|
||||
"start"
|
||||
]
|
||||
|
||||
},
|
||||
{
|
||||
"value": "bc",
|
||||
"scopes": [
|
||||
"source.infinite-loop"
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
@ -2696,8 +2710,7 @@
|
|||
"fixtures/python-regex.json",
|
||||
"fixtures/infinite-loop.json"
|
||||
],
|
||||
"desc": "TEST #38",
|
||||
"feature": "endless-loop"
|
||||
"desc": "TEST #38"
|
||||
},
|
||||
{
|
||||
"grammarScopeName": "source.css.scss",
|
||||
|
@ -3361,7 +3374,14 @@
|
|||
"grammarPath": "fixtures/forever.json",
|
||||
"lines": [
|
||||
{
|
||||
"line": "forever and ever"
|
||||
"line": "forever and ever",
|
||||
"tokens": [{
|
||||
"value": "forever and ever",
|
||||
"scopes": [
|
||||
"source.forever",
|
||||
"text"
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
@ -3377,8 +3397,7 @@
|
|||
"fixtures/python-regex.json",
|
||||
"fixtures/forever.json"
|
||||
],
|
||||
"desc": "TEST #48",
|
||||
"feature": "endless-loop"
|
||||
"desc": "TEST #48"
|
||||
},
|
||||
{
|
||||
"grammarScopeName": "source.ruby",
|
||||
|
@ -5246,22 +5265,216 @@
|
|||
"grammarScopeName": "source.thrift",
|
||||
"lines": [
|
||||
{
|
||||
"line": "exception SimpleErr {"
|
||||
"line": "exception SimpleErr {",
|
||||
"tokens": [{
|
||||
"value": "exception",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"keyword.other.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "SimpleErr",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"entity.name.type.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "{",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"punctuation.section.exception.begin.thrift"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": " 1: string message"
|
||||
"line": " 1: string message",
|
||||
"tokens": [{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "1:",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"entity.other.field-id.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "string",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"storage.type.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "message",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"variable.other.field-name.thrift"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": ""
|
||||
"line": "",
|
||||
"tokens": [{
|
||||
"value": "",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "service SimpleService {"
|
||||
"line": "service SimpleService {",
|
||||
"tokens": [{
|
||||
"value": "service",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"storage.type.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "SimpleService",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"variable.other.field-name.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "{",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": " void Simple() throws (1: SimpleErr simpleErr)"
|
||||
"line": " void Simple() throws (1: SimpleErr simpleErr)",
|
||||
"tokens": [{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "void",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"storage.type.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": " ",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "Simple",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift",
|
||||
"variable.other.field-name.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": "(",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
},
|
||||
{
|
||||
"value": ") throws (1: SimpleErr simpleErr)",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "}"
|
||||
"line": "}",
|
||||
"tokens": [{
|
||||
"value": "}",
|
||||
"scopes": [
|
||||
"source.thrift",
|
||||
"meta.exception.thrift",
|
||||
"meta.field.thrift"
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
@ -5277,14 +5490,19 @@
|
|||
"fixtures/python-regex.json",
|
||||
"fixtures/thrift.json"
|
||||
],
|
||||
"desc": "TEST #73",
|
||||
"feature": "endless-loop"
|
||||
"desc": "TEST #73"
|
||||
},
|
||||
{
|
||||
"grammarScopeName": "source.loops",
|
||||
"lines": [
|
||||
{
|
||||
"line": "test"
|
||||
"line": "test",
|
||||
"tokens": [{
|
||||
"value": "test",
|
||||
"scopes": [
|
||||
"source.loops"
|
||||
]
|
||||
}]
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
|
@ -5300,7 +5518,6 @@
|
|||
"fixtures/python-regex.json",
|
||||
"fixtures/loops.json"
|
||||
],
|
||||
"desc": "TEST #74",
|
||||
"feature": "endless-loop"
|
||||
"desc": "TEST #74"
|
||||
}
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>fileTypes</key>
|
||||
<array>
|
||||
<string>testlang</string>
|
||||
</array>
|
||||
<key>name</key>
|
||||
<string>testlang</string>
|
||||
<key>patterns</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>include</key>
|
||||
<string>#indentedVerbatimOp</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>repository</key>
|
||||
<dict>
|
||||
<key>indentedVerbatimOp</key>
|
||||
<dict>
|
||||
<key>begin</key>
|
||||
<string>^([ \t]*)(?=(.*?)\|$)</string>
|
||||
<key>end</key>
|
||||
<string>^(?!\1[ \t])(?=[ \t]*\S)</string>
|
||||
<key>name</key>
|
||||
<string>string.unquoted.verbatim.youki</string>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>scopeName</key>
|
||||
<string>text.testlang</string>
|
||||
<key>uuid</key>
|
||||
<string>159375af-d9b4-448c-a9da-d235eadf3556</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -161,6 +161,7 @@
|
|||
"fixtures/aspvbnet.plist"
|
||||
],
|
||||
"grammarPath": "fixtures/aspvbnet.plist",
|
||||
"desc": "asp",
|
||||
"lines": [
|
||||
{
|
||||
"line": "Dim tmpStr As String",
|
||||
|
@ -224,6 +225,7 @@
|
|||
"fixtures/aspvbnet.plist"
|
||||
],
|
||||
"grammarPath": "fixtures/aspvbnet.plist",
|
||||
"desc": "asp2",
|
||||
"lines": [
|
||||
{
|
||||
"line": "Dim tmpStr As String",
|
||||
|
@ -848,5 +850,72 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"grammars": [
|
||||
"fixtures/testlang12.plist"
|
||||
],
|
||||
"grammarPath": "fixtures/testlang12.plist",
|
||||
"desc": "Issue #12",
|
||||
"lines": [
|
||||
{
|
||||
"line": "[test]|",
|
||||
"tokens": [{
|
||||
"value": "[test]|",
|
||||
"scopes": [
|
||||
"text.testlang",
|
||||
"string.unquoted.verbatim.youki"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "\tverb",
|
||||
"tokens": [{
|
||||
"value": "\tverb",
|
||||
"scopes": [
|
||||
"text.testlang",
|
||||
"string.unquoted.verbatim.youki"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "asd",
|
||||
"tokens": [{
|
||||
"value": "asd",
|
||||
"scopes": [
|
||||
"text.testlang"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "asd",
|
||||
"tokens": [{
|
||||
"value": "asd",
|
||||
"scopes": [
|
||||
"text.testlang"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "[test]|",
|
||||
"tokens": [{
|
||||
"value": "[test]|",
|
||||
"scopes": [
|
||||
"text.testlang",
|
||||
"string.unquoted.verbatim.youki"
|
||||
]
|
||||
}]
|
||||
},
|
||||
{
|
||||
"line": "\tverbatim",
|
||||
"tokens": [{
|
||||
"value": "\tverbatim",
|
||||
"scopes": [
|
||||
"text.testlang",
|
||||
"string.unquoted.verbatim.youki"
|
||||
]
|
||||
}]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче