Skip to content

Commit 359165f

Browse files
authored
Merge pull request #49 from Pythagora-io/optimize
optimize traverseDirectory() for unit tests
2 parents 898fdb7 + a2bf4c6 commit 359165f

File tree

3 files changed

+77
-39
lines changed

3 files changed

+77
-39
lines changed

README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@ or to run tests from a specific file or a folder, run `npx jest <PATH_TO_FILE_OR
160160
<br><br>
161161
- **Tests help me think about my code - I don't want to generate them automatically**
162162
- That's the best thing about Pythagora - it actually does help you think about the code. Just, you don't need to spend time writing tests. This happened to us, who created Pythagora - we coded it as fast as possible but when we added unit test generation, we realized that it cannot create tests for some functions. So, we refactored the code and made it more modular so that unit tests can be generated for it.
163-
<br><br>
164-
- **What tests are the best**
165-
- That's the best thing about Pythagora - it actually does help you think about the code. Just, you don't need to spend time writing tests. This happened to us, who created Pythagora - we coded it as fast as possible but when we added unit test generation, we realized that it cannot create tests for some functions. So, we refactored the code and made it more modular so that unit tests can be generated for it.
166163

167164
<br>
168165

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@
1717
"publish-pythagora": "src/bin/publish.bash",
1818
"prepublishOnly": "node src/bin/prepublish.js",
1919
"postinstall": "node src/bin/postinstall.js",
20-
"test": "npx jest ./pythagora_tests/ --coverage"
20+
"test": "npx jest ./pythagora_tests/ --coverage",
21+
"generate-unit-tests": "npx pythagora --unit-tests --path ./"
2122
},
2223
"bin": {
2324
"pythagora": "src/bin/run.js"

src/helpers/unitTests.js

Lines changed: 75 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
const fs = require('fs');
22
const path = require('path');
33
const _ = require('lodash');
4-
const { getUnitTests, checkForAPIKey} = require('./api');
4+
const API = require('./api');
55
const {PYTHAGORA_UNIT_DIR} = require("../const/common");
66
const generator = require("@babel/generator").default;
77
const {checkDirectoryExists} = require("../utils/common");
@@ -34,13 +34,45 @@ let functionList = {},
3434
force
3535
;
3636

37-
async function processFile(filePath) {
37+
function resolveFilePath(filePath, extension) {
38+
if (fs.existsSync(filePath)) {
39+
return filePath;
40+
}
41+
42+
const filePathWithExtension = `${filePath}${extension}`;
43+
if (fs.existsSync(filePathWithExtension)) {
44+
return filePathWithExtension;
45+
}
46+
47+
return undefined;
48+
}
49+
50+
async function processFile(filePath, filesToProcess) {
3851
try {
3952
let exportsFn = [];
4053
let exportsObj = [];
4154
let functions = [];
4255
let ast = await getAstFromFilePath(filePath);
4356
let syntaxType = await getModuleTypeFromFilePath(ast);
57+
let extension = path.extname(filePath);
58+
59+
// Analyze dependencies
60+
ast.program.body.forEach(node => {
61+
if (node.type === "ImportDeclaration") {
62+
let importedFile = path.resolve(path.dirname(filePath), node.source.value);
63+
importedFile = resolveFilePath(importedFile, extension);
64+
if (importedFile && !filesToProcess.includes(importedFile)) {
65+
filesToProcess.push(importedFile);
66+
}
67+
} else if (node.type === "VariableDeclaration" && node.declarations[0].init.type === "CallExpression" && node.declarations[0].init.callee.name === "require") {
68+
let importedFile = path.resolve(path.dirname(filePath), node.declarations[0].init.arguments[0].value);
69+
importedFile = resolveFilePath(importedFile, extension);
70+
if (importedFile && !filesToProcess.includes(importedFile)) {
71+
filesToProcess.push(importedFile);
72+
}
73+
}
74+
});
75+
4476
processAst(ast, (funcName, path, type) => {
4577
if (type === 'exportFn' || type === 'exportFnDef') {
4678
exportsFn.push(funcName);
@@ -116,7 +148,7 @@ async function reformatDataForPythagoraAPI(funcData, filePath, testFilePath) {
116148
return funcData;
117149
}
118150

119-
async function createTests(filePath, prefix, funcToTest) {
151+
async function createTests(filePath, prefix, funcToTest, processingFunction = 'getUnitTests') {
120152
try {
121153
let extension = path.extname(filePath);
122154
let ast = await getAstFromFilePath(filePath);
@@ -173,7 +205,7 @@ async function createTests(filePath, prefix, funcToTest) {
173205
}
174206

175207
let formattedData = await reformatDataForPythagoraAPI(funcData, filePath, getTestFolderPath(filePath, rootPath));
176-
let { tests, error } = await getUnitTests(formattedData, (content) => {
208+
let { tests, error } = await API[processingFunction](formattedData, (content) => {
177209
scrollableContent.setContent(content);
178210
scrollableContent.setScrollPerc(100);
179211
screen.render();
@@ -217,42 +249,49 @@ async function saveTests(filePath, name, testData) {
217249
return testPath;
218250
}
219251

220-
async function traverseDirectory(directory, onlyCollectFunctionData, prefix = '', funcName) {
221-
if (await checkPathType(directory) === 'file' && !onlyCollectFunctionData) {
222-
if (!processExtensions.includes(path.extname(directory))) throw new Error('File extension is not supported');
252+
async function traverseDirectory(file, onlyCollectFunctionData, prefix = '', funcName, filesToProcess = [file], processingFunction) {
253+
if (await checkPathType(file) === 'file' && !onlyCollectFunctionData) {
254+
if (!processExtensions.includes(path.extname(file))) throw new Error('File extension is not supported');
223255
const newPrefix = `| ${prefix}| `;
224-
return await createTests(directory, newPrefix, funcName);
256+
return await createTests(file, newPrefix, funcName, processingFunction);
225257
}
226-
const files = fs.readdirSync(directory);
227-
for (const file of files) {
228-
const absolutePath = path.join(directory, file);
229-
const stat = fs.statSync(absolutePath);
230-
const isLast = files.indexOf(file) === files.length - 1;
231258

232-
if (ignoreFilesEndingWith.some(ending => file.endsWith(ending))) continue;
259+
const absolutePath = path.resolve(file);
260+
const stat = fs.statSync(absolutePath);
261+
const isLast = filesToProcess.length === 0;
262+
263+
if (ignoreFilesEndingWith.some(ending => file.endsWith(ending))) return;
264+
265+
if (stat.isDirectory()) {
266+
if (ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === '.') return;
267+
console.log(file)
233268

234-
if (stat.isDirectory()) {
235-
if (ignoreFolders.includes(path.basename(absolutePath)) || path.basename(absolutePath).charAt(0) === '.') continue;
269+
if (onlyCollectFunctionData && isPathInside(path.dirname(queriedPath), absolutePath)) {
270+
updateFolderTree(prefix, isLast, absolutePath);
271+
}
272+
273+
const newPrefix = isLast ? `${prefix} ` : `${prefix}| `;
274+
const directoryFiles = fs.readdirSync(absolutePath);
275+
filesToProcess.push(...directoryFiles.map(f => path.join(absolutePath, f)));
276+
} else {
277+
if (!processExtensions.includes(path.extname(absolutePath))) return;
278+
console.log(file)
236279

237-
if (onlyCollectFunctionData && isPathInside(path.dirname(queriedPath), absolutePath)) {
280+
if (onlyCollectFunctionData) {
281+
if (isPathInside(path.dirname(queriedPath), absolutePath)) {
238282
updateFolderTree(prefix, isLast, absolutePath);
239283
}
240-
241-
const newPrefix = isLast ? `${prefix} ` : `${prefix}| `;
242-
await traverseDirectory(absolutePath, onlyCollectFunctionData, newPrefix, funcName);
284+
await processFile(absolutePath, filesToProcess);
243285
} else {
244-
if (!processExtensions.includes(path.extname(absolutePath))) continue;
245-
if (onlyCollectFunctionData) {
246-
if (isPathInside(path.dirname(queriedPath), absolutePath)) {
247-
updateFolderTree(prefix, isLast, absolutePath);
248-
}
249-
await processFile(absolutePath);
250-
} else {
251-
const newPrefix = isLast ? `| ${prefix} ` : `| ${prefix}| `;
252-
await createTests(absolutePath, newPrefix, funcName);
253-
}
286+
const newPrefix = isLast ? `| ${prefix} ` : `| ${prefix}| `;
287+
await createTests(absolutePath, newPrefix, funcName, processingFunction);
254288
}
255289
}
290+
291+
while (filesToProcess.length > 0) {
292+
const nextFile = filesToProcess.shift();
293+
await traverseDirectory(nextFile, onlyCollectFunctionData, prefix, funcName, filesToProcess, processingFunction);
294+
}
256295
}
257296

258297
function updateFolderTree(prefix, isLast, absolutePath) {
@@ -268,19 +307,20 @@ async function getFunctionsForExport(dirPath) {
268307
return functionList;
269308
}
270309

271-
async function generateTestsForDirectory(args) {
310+
async function generateTestsForDirectory(args, processingFunction = 'getUnitTests') {
272311
let pathToProcess = args.path,
273312
funcName = args.func;
274313
force = args.force;
275314

276-
checkForAPIKey();
315+
API.checkForAPIKey();
277316
queriedPath = path.resolve(pathToProcess);
278317
rootPath = process.cwd();
279318
({ screen, spinner, scrollableContent } = initScreenForUnitTests());
280319

281-
await traverseDirectory(rootPath, true); // first pass: collect all function names and codes
282-
await traverseDirectory(rootPath, true); // second pass: collect all related functions
283-
await traverseDirectory(queriedPath, false, undefined, funcName); // second pass: print functions and their related functions
320+
let filesToProcess = [];
321+
await traverseDirectory(queriedPath, true, undefined, funcName, filesToProcess, processingFunction);
322+
await traverseDirectory(queriedPath, true, undefined, funcName, filesToProcess, processingFunction);
323+
await traverseDirectory(queriedPath, false, undefined, funcName, filesToProcess, processingFunction);
284324

285325
screen.destroy();
286326
process.stdout.write('\x1B[2J\x1B[0f');

0 commit comments

Comments
 (0)