Skip to content

Commit

Permalink
Merge pull request #601 from GuillaumeGomez/test-examples
Browse files Browse the repository at this point in the history
Add test to check goml examples are valid
  • Loading branch information
GuillaumeGomez authored Apr 4, 2024
2 parents 9fdd000 + 7257897 commit b1068ca
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 111 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The other tests are checks for the framework's code itself:

For `ui` tests, you can filter which ones you want to run by passing the test name as argument:

```
```console
$ npm run ui-test debug.goml debug2.goml
```

Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,17 +33,17 @@ npm install browser-ui-test

In case you can't install puppeteer "normally", you can give a try to `--unsafe-perm=true`:

```bash
```console
$ npm install puppeteer --unsafe-perm=true
```

## Usage

You can either use this framework by using it as dependency or running it directly. In both cases you'll need to write some `.goml` scripts. It looks like this:

```
```goml
go-to: "https://somewhere.com" // go to this url
text: ("#button", "hello") // set text of element #button
set-text: ("#button", "hello") // set text of element #button
assert-text: ("#button", "hello") // check if #button element's text has been set to "hello"
```

Expand Down
136 changes: 67 additions & 69 deletions goml-script.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"parser-test": "node tools/parser.js",
"exported-test": "node tools/exported_items.js",
"generated-test": "node tools/generated.js",
"examples-test": "node tools/check-examples.js",
"lint": "eslint src tools",
"lint-fix": "eslint --fix src tools"
},
Expand Down
15 changes: 15 additions & 0 deletions src/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -410,10 +410,25 @@ class ParserWithContext {
}
}

function parseTest(testName, testPath, logs, options, content) {
try {
const parser = new ParserWithContext(testPath, options, content);
return {
'file': testName,
'parser': parser,
};
} catch (err) {
logs.append(testName + '... FAILED (exception occured)');
logs.append(`${err.message}\n${err.stack}`);
}
return null;
}

const EXPORTS = {
'ParserWithContext': ParserWithContext,
'COLOR_CHECK_ERROR': consts.COLOR_CHECK_ERROR,
'ORDERS': ORDERS,
'parseTest': parseTest,
};

for (const func of Object.values(ORDERS)) {
Expand Down
58 changes: 20 additions & 38 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
#!/usr/bin/env node

const fs = require('fs');
const PNG = require('pngjs').PNG;
const { PNG } = require('pngjs');
const parser = require('./parser.js');
const commands_parser = require('./commands.js');
const { COLOR_CHECK_ERROR, parseTest } = require('./commands.js');
const { innerParseScreenshot } = require('./commands/general.js');
const utils = require('./utils.js');
const Options = require('./options.js').Options;
const {
print,
add_warning,
loadPuppeteer,
getFileInfo,
escapeBackslahes,
extractFileNameWithoutExtension,
} = require('./utils.js');
const { Options } = require('./options.js');
const {Debug, Logs} = require('./logs.js');
const add_warn = utils.add_warning;
const process = require('process');
const print = utils.print;
const path = require('path');
const consts = require('./consts.js');
const Module = require('module');
Expand Down Expand Up @@ -68,7 +73,7 @@ function save_failure(folderIn, failuresFolder, newImage) {
try {
fs.renameSync(path.join(folderIn, newImage), path.join(failuresFolder, newImage));
} catch (err) {
add_warn(`Error while trying to move files: "${err.message}"`);
add_warning(`Error while trying to move files: "${err.message}"`);
// failed to move files...
return false;
}
Expand All @@ -88,20 +93,6 @@ function getGlobalStyle(showText) {
return css;
}

function parseTest(testName, testPath, logs, options, content) {
try {
const parser = new commands_parser.ParserWithContext(testPath, options, content);
return {
'file': testName,
'parser': parser,
};
} catch (err) {
logs.append(testName + '... FAILED (exception occured)');
logs.append(`${err.message}\n${err.stack}`);
}
return null;
}

function createFolderIfNeeded(path) {
if (path !== '' && fs.existsSync(path) === false) {
try {
Expand Down Expand Up @@ -176,15 +167,6 @@ async function runInstruction(loadedInstruction, page, extras) {
await loadedInstruction(page, extras);
}

function getFileInfo(context_parser, line, isExact = true) {
const file = utils.stripCommonPathsPrefix(context_parser.getCurrentFile());
const file_s = file.length > 0 ? `\`${file}\` ` : '';
if (isExact) {
return `${file_s}line ${line}`;
}
return `${file_s}around line ${line}`;
}

async function runAllCommands(loaded, logs, options, browser) {
const commandLogs = new Logs(false);
logs.append(loaded['file'] + '... ');
Expand Down Expand Up @@ -217,7 +199,7 @@ async function runAllCommands(loaded, logs, options, browser) {
'variables': options.variables,
'setVariable': (varName, value) => {
if (typeof value === 'string') {
value = utils.escapeBackslahes(value);
value = escapeBackslahes(value);
}
extras.variables[varName] = value;
},
Expand Down Expand Up @@ -344,7 +326,7 @@ ${error.message}\n`;
try {
await runInstruction(loadedInstruction, page, extras);
} catch (err) { // execution error
if (err === commands_parser.COLOR_CHECK_ERROR) {
if (err === COLOR_CHECK_ERROR) {
error_log += `[ERROR] ${getFileInfo(context_parser, line_number)}: \
${err}\n`;
stopLoop = true;
Expand Down Expand Up @@ -563,13 +545,13 @@ async function innerRunTests(logs, options, browser) {
try {
const needCloseBrowser = browser === null;
if (browser === null) {
browser = await utils.loadPuppeteer(options);
browser = await loadPuppeteer(options);
}
const testsQueue = [];

for (const file of allFiles) {
total += 1;
const testName = utils.extractFileNameWithoutExtension(file);
const testName = extractFileNameWithoutExtension(file);
const optionsCopy = options.clone();

// To make the Options type validation happy.
Expand Down Expand Up @@ -652,7 +634,7 @@ async function innerRunTestCode(

const needCloseBrowser = browser === null;
if (browser === null) {
browser = await utils.loadPuppeteer(options);
browser = await loadPuppeteer(options);
}
const ret = await runAllCommands(loaded, logs, options, browser);

Expand Down Expand Up @@ -754,7 +736,7 @@ async function runTest(testPath, extras = null) {
}

return innerRunTestCode(
utils.extractFileNameWithoutExtension(testPath),
extractFileNameWithoutExtension(testPath),
testPath,
optionsCopy,
browser,
Expand Down Expand Up @@ -811,7 +793,7 @@ if (require.main === module) {
}
process.exit(1);
}
utils.loadPuppeteer(options).then(browser => {
loadPuppeteer(options).then(browser => {
runTests({'options': options, 'browser': browser, 'showLogs': true}).then(x => {
const [_output, nb_failures] = x;
process.exit(nb_failures);
Expand All @@ -835,6 +817,6 @@ if (require.main === module) {
'runTestCode': runTestCode,
'runTests': runTests,
'Options': Options,
'loadBrowser': utils.loadPuppeteer,
'loadBrowser': loadPuppeteer,
};
}
10 changes: 10 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,15 @@ function hasError(x) {
return x.error !== undefined && x.error !== null;
}

function getFileInfo(context_parser, line, isExact = true) {
const file = stripCommonPathsPrefix(context_parser.getCurrentFile());
const file_s = file.length > 0 ? `\`${file}\` ` : '';
if (isExact) {
return `${file_s}line ${line}`;
}
return `${file_s}around line ${line}`;
}

module.exports = {
'addSlash': addSlash,
'getCurrentDir': getCurrentDir,
Expand All @@ -198,4 +207,5 @@ module.exports = {
'stripCommonPathsPrefix': stripCommonPathsPrefix,
'plural': plural,
'hasError': hasError,
'getFileInfo': getFileInfo,
};
1 change: 1 addition & 0 deletions tools/api.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const process = require('process');
process.env.debug_tests = '1'; // We enable this to get all items from `commands.js`.

const parserFuncs = require('../src/commands.js');
const Options = require('../src/options.js').Options;
const { RESERVED_VARIABLE_NAME } = require('../src/utils.js');
Expand Down
151 changes: 151 additions & 0 deletions tools/check-examples.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
const process = require('process');
process.env.debug_tests = '1'; // We enable this to get all items from `commands.js`.

const {Assert, plural, print} = require('./utils.js');
const { getFileInfo } = require('../src/utils.js');
const { parseTest, BEFORE_GOTO } = require('../src/commands.js');
const { Options } = require('../src/options.js');
const { Logs } = require('../src/logs.js');
const fs = require('fs');
const path = require('path');

function getAllFiles(folderPath, level) {
const files = [];

if (level > 1) {
return files;
}
fs.readdirSync(folderPath).forEach(function(file) {
const fullPath = path.join(folderPath, file);
const stat = fs.lstatSync(fullPath);
if (stat.isFile()) {
if (file.endsWith('.md')) {
files.push(fullPath);
}
} else if (stat.isDirectory()) {
files.push(...getAllFiles(fullPath, level + 1));
}
});
return files;
}

function checkContent(x, example, file, line, firstCommand) {
const logs = new Logs(false);
const fileAndLine = `${file}:${line}`;
const extra = BEFORE_GOTO.includes(firstCommand) ? '' : 'go-to: "/a"\n';

try {
const loaded = parseTest(fileAndLine, file, logs, new Options(), extra + example);
// FIXME: it's the same code as inside `src/index.js`. Better make a common function!
if (loaded === null) {
x.addError(`${fileAndLine}: Failed to parse example: ${logs.logs}`);
return;
} else if (loaded.parser.get_parser_errors().length !== 0) {
const errors = loaded.parser.get_parser_errors()
.map(e => `${getFileInfo(loaded.parser, e.line)}: ${e.message}`)
.join('\n');
x.addError(`${fileAndLine}: Syntax errors: ${errors}`);
return;
}
const context_parser = loaded['parser'];
// Variables used in some examples.
context_parser.variables['variable'] = 1;
context_parser.variables['class_var'] = 1;
context_parser.variables['window_height'] = 1;
context_parser.variables['width'] = 1;
context_parser.variables['height'] = 1;
context_parser.variables['A_VARIABLE'] = 'a';
context_parser.variables['DOC_PATH'] = './a';

for (let nb_commands = 0;; ++nb_commands) {
const command = context_parser.get_next_command();
if (command === null) {
if (nb_commands === 0) {
x.addError(`${fileAndLine}: FAILED (No command to execute)`);
return;
}
break;
}
// FIXME: it's the same code as inside `src/index.js`. Better make a common function!
if (command['error'] !== undefined) {
x.addError(
`${fileAndLine}: [ERROR] ${getFileInfo(context_parser, command['line'])}: \
${command['error']}`);
return;
} else if (command['errors'] !== undefined && command['errors'].length > 0) {
const error = command['errors'].map(e => {
return `[ERROR] ${getFileInfo(context_parser, e['line'])}: ${e['message']}`;
}).join('\n');
x.addError(`${fileAndLine}: ${error}`);
return;
}
}
} catch (err) {
x.addError(`${fileAndLine}: ${err}`);
return;
}
x.addSuccess();
}

function checkFileExamples(x, filePath) {
const lines = fs.readFileSync(filePath, 'utf8').split('\n');

for (let i = 0, len = lines.length; i < len; ++i) {
const line = lines[i];

if (line.startsWith('```')) {
const lineStart = i + 1;
const block = [];
i += 1;
let command = null;
for (; i < len; ++i) {
if (lines[i].startsWith('```')) {
break;
} else if (command === null && lines[i].trim().length > 0) {
const c = lines[i].trim()[0];
if (c >= 'a' && c <= 'z') {
command = lines[i].split(':')[0];
}
}
block.push(lines[i]);
}
if (line === '```' || line === '```goml') {
checkContent(x, block.join('\n'), filePath, lineStart, command);
}
}
}
}

async function checkExamples(x) {
x.startTestSuite('examples', false);
print('=> Starting code examples checks...');
print('');

const files = getAllFiles(path.join(__dirname, '..'), 0);
for (const file of files) {
x.startTestSuite(file);
try {
checkFileExamples(x, file);
x.endTestSuite();
} catch (err) {
x.endTestSuite(false, true);
}
}

print('');
print(`<= Ending ${x.getTotalRanTests()} ${plural('test', x.getTotalRanTests())} with ` +
`${x.getTotalErrors()} ${plural('error', x.getTotalErrors())}`);

return x.getTotalErrors();
}

if (require.main === module) {
const x = new Assert();
checkExamples(x).then(nbErrors => {
process.exit(nbErrors !== 0 ? 1 : 0);
});
} else {
module.exports = {
'check': checkExamples,
};
}
4 changes: 4 additions & 0 deletions tools/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ class Assert {
this._incrError();
}

addSuccess() {
this._addTest();
}

_addTest() {
if (this.testSuite.length > 0) {
this.testSuite[this.testSuite.length - 1].ranTests += 1;
Expand Down

0 comments on commit b1068ca

Please sign in to comment.