Skip to content

Commit

Permalink
fix: glob backward compatibility (#4775)
Browse files Browse the repository at this point in the history
  • Loading branch information
kobenguyent authored Jan 29, 2025
1 parent 236dc72 commit 7030c7f
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 127 deletions.
22 changes: 12 additions & 10 deletions lib/codecept.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { existsSync, readFileSync } = require('fs')
const glob = require('glob')
const { globSync } = require('glob')
const fsPath = require('path')
const { resolve } = require('path')

Expand Down Expand Up @@ -168,15 +168,17 @@ class Codecept {
}

for (pattern of patterns) {
glob.sync(pattern, options).forEach(file => {
if (file.includes('node_modules')) return
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file)
}
if (!this.testFiles.includes(fsPath.resolve(file))) {
this.testFiles.push(fsPath.resolve(file))
}
})
if (pattern) {
globSync(pattern, options).forEach(file => {
if (file.includes('node_modules')) return
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file)
}
if (!this.testFiles.includes(fsPath.resolve(file))) {
this.testFiles.push(fsPath.resolve(file))
}
})
}
}
}

Expand Down
138 changes: 69 additions & 69 deletions lib/command/gherkin/snippets.js
Original file line number Diff line number Diff line change
@@ -1,113 +1,113 @@
const escapeStringRegexp = require('escape-string-regexp');
const fs = require('fs');
const Gherkin = require('@cucumber/gherkin');
const Messages = require('@cucumber/messages');
const glob = require('glob');
const fsPath = require('path');
const escapeStringRegexp = require('escape-string-regexp')
const fs = require('fs')
const Gherkin = require('@cucumber/gherkin')
const Messages = require('@cucumber/messages')
const { globSync } = require('glob')
const fsPath = require('path')

const { getConfig, getTestRoot } = require('../utils');
const Codecept = require('../../codecept');
const output = require('../../output');
const { matchStep } = require('../../mocha/bdd');
const { getConfig, getTestRoot } = require('../utils')
const Codecept = require('../../codecept')
const output = require('../../output')
const { matchStep } = require('../../mocha/bdd')

const uuidFn = Messages.IdGenerator.uuid();
const builder = new Gherkin.AstBuilder(uuidFn);
const matcher = new Gherkin.GherkinClassicTokenMatcher();
const parser = new Gherkin.Parser(builder, matcher);
parser.stopAtFirstError = false;
const uuidFn = Messages.IdGenerator.uuid()
const builder = new Gherkin.AstBuilder(uuidFn)
const matcher = new Gherkin.GherkinClassicTokenMatcher()
const parser = new Gherkin.Parser(builder, matcher)
parser.stopAtFirstError = false

module.exports = function (genPath, options) {
const configFile = options.config || genPath;
const testsPath = getTestRoot(configFile);
const config = getConfig(configFile);
if (!config) return;
const configFile = options.config || genPath
const testsPath = getTestRoot(configFile)
const config = getConfig(configFile)
if (!config) return

const codecept = new Codecept(config, {});
codecept.init(testsPath);
const codecept = new Codecept(config, {})
codecept.init(testsPath)

if (!config.gherkin) {
output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it');
process.exit(1);
output.error('Gherkin is not enabled in config. Run `codecept gherkin:init` to enable it')
process.exit(1)
}
if (!config.gherkin.steps || !config.gherkin.steps[0]) {
output.error('No gherkin steps defined in config. Exiting');
process.exit(1);
output.error('No gherkin steps defined in config. Exiting')
process.exit(1)
}
if (!options.feature && !config.gherkin.features) {
output.error('No gherkin features defined in config. Exiting');
process.exit(1);
output.error('No gherkin features defined in config. Exiting')
process.exit(1)
}
if (options.path && !config.gherkin.steps.includes(options.path)) {
output.error(`You must include ${options.path} to the gherkin steps in your config file`);
process.exit(1);
output.error(`You must include ${options.path} to the gherkin steps in your config file`)
process.exit(1)
}

const files = [];
glob.sync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
const files = []
globSync(options.feature || config.gherkin.features, { cwd: options.feature ? '.' : global.codecept_dir }).forEach(file => {
if (!fsPath.isAbsolute(file)) {
file = fsPath.join(global.codecept_dir, file);
file = fsPath.join(global.codecept_dir, file)
}
files.push(fsPath.resolve(file));
});
output.print(`Loaded ${files.length} files`);
files.push(fsPath.resolve(file))
})
output.print(`Loaded ${files.length} files`)

const newSteps = new Map();
const newSteps = new Map()

const parseSteps = steps => {
const newSteps = [];
let currentKeyword = '';
const newSteps = []
let currentKeyword = ''
for (const step of steps) {
if (step.keyword.trim() === 'And') {
if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`);
step.keyword = currentKeyword;
if (!currentKeyword) throw new Error(`There is no active keyword for step '${step.text}'`)
step.keyword = currentKeyword
}
currentKeyword = step.keyword;
currentKeyword = step.keyword
try {
matchStep(step.text);
matchStep(step.text)
} catch (err) {
let stepLine;
let stepLine
if (/[{}()/]/.test(step.text)) {
stepLine = escapeStringRegexp(step.text)
.replace(/\//g, '\\/')
.replace(/\"(.*?)\"/g, '"(.*?)"')
.replace(/(\d+\\\.\d+)/, '(\\d+\\.\\d+)')
.replace(/ (\d+) /, ' (\\d+) ');
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true });
.replace(/ (\d+) /, ' (\\d+) ')
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: true })
} else {
stepLine = step.text
.replace(/\"(.*?)\"/g, '{string}')
.replace(/(\d+\.\d+)/, '{float}')
.replace(/ (\d+) /, ' {int} ');
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false });
.replace(/ (\d+) /, ' {int} ')
stepLine = Object.assign(stepLine, { type: step.keyword.trim(), location: step.location, regexp: false })
}
newSteps.push(stepLine);
newSteps.push(stepLine)
}
}
return newSteps;
};
return newSteps
}

const parseFile = file => {
const ast = parser.parse(fs.readFileSync(file).toString());
const ast = parser.parse(fs.readFileSync(file).toString())
for (const child of ast.feature.children) {
if (child.scenario.keyword === 'Scenario Outline') continue; // skip scenario outline
if (child.scenario.keyword === 'Scenario Outline') continue // skip scenario outline
parseSteps(child.scenario.steps)
.map(step => {
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) });
return Object.assign(step, { file: file.replace(global.codecept_dir, '').slice(1) })
})
.map(step => newSteps.set(`${step.type}(${step})`, step));
.map(step => newSteps.set(`${step.type}(${step})`, step))
}
};
}

files.forEach(file => parseFile(file));
files.forEach(file => parseFile(file))

let stepFile = options.path || config.gherkin.steps[0];
let stepFile = options.path || config.gherkin.steps[0]
if (!fs.existsSync(stepFile)) {
output.error(`Please enter a valid step file path ${stepFile}`);
process.exit(1);
output.error(`Please enter a valid step file path ${stepFile}`)
process.exit(1)
}

if (!fsPath.isAbsolute(stepFile)) {
stepFile = fsPath.join(global.codecept_dir, stepFile);
stepFile = fsPath.join(global.codecept_dir, stepFile)
}

const snippets = [...newSteps.values()]
Expand All @@ -117,18 +117,18 @@ module.exports = function (genPath, options) {
${step.type}(${step.regexp ? '/^' : "'"}${step}${step.regexp ? '$/' : "'"}, () => {
// From "${step.file}" ${JSON.stringify(step.location)}
throw new Error('Not implemented yet');
});`;
});
});`
})

if (!snippets.length) {
output.print('No new snippets found');
return;
output.print('No new snippets found')
return
}
output.success(`Snippets generated: ${snippets.length}`);
output.print(snippets.join('\n'));
output.success(`Snippets generated: ${snippets.length}`)
output.print(snippets.join('\n'))

if (!options.dryRun) {
output.success(`Snippets added to ${output.colors.bold(stepFile)}`);
fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n');
output.success(`Snippets added to ${output.colors.bold(stepFile)}`)
fs.writeFileSync(stepFile, fs.readFileSync(stepFile).toString() + snippets.join('\n') + '\n')
}
};
}
93 changes: 48 additions & 45 deletions lib/command/run-multiple/chunk.js
Original file line number Diff line number Diff line change
@@ -1,91 +1,94 @@
const glob = require('glob');
const path = require('path');
const fs = require('fs');
const { globSync } = require('glob')
const path = require('path')
const fs = require('fs')

/**
* Splits a list to (n) parts, defined via the size argument.
*/
const splitFiles = (list, size) => {
const sets = [];
const chunks = list.length / size;
let i = 0;
const sets = []
const chunks = list.length / size
let i = 0

while (i < chunks) {
sets[i] = list.splice(0, size);
i++;
sets[i] = list.splice(0, size)
i++
}

return sets;
};
return sets
}

/**
* Executes a glob pattern and pushes the results to a list.
*/
const findFiles = (pattern) => {
const files = [];
const findFiles = pattern => {
const files = []

glob.sync(pattern).forEach((file) => {
files.push(path.resolve(file));
});
globSync(pattern).forEach(file => {
files.push(path.resolve(file))
})

return files;
};
return files
}

/**
* Joins a list of files to a valid glob pattern
*/
const flattenFiles = (list) => {
const pattern = list.join(',');
return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern;
};
const flattenFiles = list => {
const pattern = list.join(',')
return pattern.indexOf(',') > -1 ? `{${pattern}}` : pattern
}

/**
* Greps a file by its content, checks if Scenario or Feature text'
* matches the grep text.
*/
const grepFile = (file, grep) => {
const contents = fs.readFileSync(file, 'utf8');
const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g'); // <- How future proof/solid is this?
return !!pattern.exec(contents);
};
const contents = fs.readFileSync(file, 'utf8')
const pattern = new RegExp(`((Scenario|Feature)\(.*${grep}.*\))`, 'g') // <- How future proof/solid is this?
return !!pattern.exec(contents)
}

const mapFileFormats = (files) => {
const mapFileFormats = files => {
return {
gherkin: files.filter(file => file.match(/\.feature$/)),
js: files.filter(file => file.match(/\.t|js$/)),
};
};
}
}

/**
* Creates a list of chunks incl. configuration by either dividing a list of scenario
* files by the passed number or executing a usder deifned function to perform
* the splitting.
*/
const createChunks = (config, patterns = []) => {
const files = patterns.filter(pattern => !!pattern).map((pattern) => {
return findFiles(pattern).filter((file) => {
return config.grep ? grepFile(file, config.grep) : true;
});
}).reduce((acc, val) => acc.concat(val), []);
const files = patterns
.filter(pattern => !!pattern)
.map(pattern => {
return findFiles(pattern).filter(file => {
return config.grep ? grepFile(file, config.grep) : true
})
})
.reduce((acc, val) => acc.concat(val), [])

let chunks = [];
let chunks = []
if (typeof config.chunks === 'function') {
chunks = config.chunks.call(this, files);
chunks = config.chunks.call(this, files)
} else if (typeof config.chunks === 'number' || typeof config.chunks === 'string') {
chunks = splitFiles(files, Math.ceil(files.length / config.chunks));
chunks = splitFiles(files, Math.ceil(files.length / config.chunks))
} else {
throw new Error('chunks is neither a finite number or a valid function');
throw new Error('chunks is neither a finite number or a valid function')
}

const chunkConfig = { ...config };
delete chunkConfig.chunks;
const chunkConfig = { ...config }
delete chunkConfig.chunks

return chunks.map((chunkFiles) => {
const { js, gherkin } = mapFileFormats(chunkFiles);
return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } };
});
};
return chunks.map(chunkFiles => {
const { js, gherkin } = mapFileFormats(chunkFiles)
return { ...chunkConfig, tests: flattenFiles(js), gherkin: { features: flattenFiles(gherkin) } }
})
}

module.exports = {
createChunks,
};
}
4 changes: 2 additions & 2 deletions lib/container.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const glob = require('glob')
const { globSync } = require('glob')
const path = require('path')
const debug = require('debug')('codeceptjs:container')
const { MetaStep } = require('./step')
Expand Down Expand Up @@ -464,7 +464,7 @@ function loadGherkinSteps(paths) {
} else {
const folderPath = paths.startsWith('.') ? path.join(global.codecept_dir, paths) : ''
if (folderPath !== '') {
glob.sync(folderPath).forEach(file => {
globSync(folderPath).forEach(file => {
loadSupportObject(file, `Step Definition from ${file}`)
})
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@
"figures": "3.2.0",
"fn-args": "4.0.0",
"fs-extra": "11.3.0",
"glob": "^11.0.1",
"glob": ">=9.0.0 <12",
"fuse.js": "^7.0.0",
"html-minifier-terser": "7.2.0",
"inquirer": "6.5.2",
Expand Down

0 comments on commit 7030c7f

Please sign in to comment.